Skip to content

Commit dc8fef3

Browse files
committed
完善系统音量控制
1 parent 3f064ff commit dc8fef3

File tree

6 files changed

+251
-119
lines changed

6 files changed

+251
-119
lines changed

app/Language/modules/voice_settings.py

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
"name": "语音引擎",
1212
"description": "选择语音合成引擎类型",
1313
},
14-
"volume_group": {"name": "音量设置", "description": "调整语音播报音量大小"},
14+
"volume_group": {"name": "语音设置", "description": "调整语音播报相关设置"},
1515
"voice_engine": {
1616
"name": "语音引擎",
1717
"description": "选择语音合成引擎类型",
1818
"combo_items": ["系统TTS", "Edge TTS"],
1919
},
2020
"edge_tts_voice_name": {
21-
"name": "Edge TTS-语音名称",
22-
"description": "选择Edge TTS语音播报角色",
21+
"name": "Edge TTS 语音角色",
22+
"description": "选择Edge TTS的语音播报角色",
2323
"combo_items": [
2424
"zh-CN-XiaoxiaoNeural",
2525
"zh-CN-YunxiNeural",
@@ -28,21 +28,16 @@
2828
"en-US-GuyNeural",
2929
],
3030
},
31-
"voice_playback": {
32-
"name": "语音播放设备",
33-
"description": "选择语音播报播放设备",
34-
"combo_items": ["系统默认", "扬声器", "耳机", "蓝牙设备"],
35-
},
36-
"volume_size": {"name": "播报音量", "description": "调整语音播报音量大小"},
37-
"speech_rate": {"name": "语速调节", "description": "调整语音播报语速"},
31+
"volume_size": {"name": "播报音量", "description": "调整语音播报的音量大小"},
32+
"speech_rate": {"name": "语速调节", "description": "调整语音播报的语速"},
3833
"system_volume_control": {
3934
"name": "系统音量控制",
40-
"description": "选择要控制的系统音量类型",
41-
"combo_items": ["主音量", "应用音量", "系统音效", "麦克风音量"],
35+
"description": "是否开启系统音量自动控制",
36+
"switchbutton_name": {"enable": "", "disable": ""},
4237
},
4338
"system_volume_size": {
4439
"name": "系统音量大小",
45-
"description": "调整系统音量大小",
40+
"description": "设置系统音量的大小",
4641
},
4742
},
4843
"EN_US": {
@@ -119,10 +114,15 @@
119114
specific_announcements = {
120115
"ZH_CN": {
121116
"title": {"name": "特定播报设置", "description": "配置特定结果的语音播报"},
122-
"enabled": {"name": "开关", "description": "开启或关闭语音播报功能"},
117+
"enabled": {
118+
"name": "特定播报开关",
119+
"description": "开启或关闭特定结果语音播报的总开关",
120+
"switchbutton_name": {"enable": "", "disable": ""},
121+
},
122+
"header": {"name": "开关"},
123123
"mode": {
124124
"name": "播报模式",
125-
"description": "选择语音播报的模式",
125+
"description": "选择语音播报模式",
126126
"combo_items": ["点名模式", "抽奖模式"],
127127
},
128128
"roll_call_title": {
@@ -133,26 +133,29 @@
133133
"name": "选择班级/奖池",
134134
"description": "选择要管理TTS的班级或奖池",
135135
},
136-
"id_field": {"name": "学号", "description": "是否在播报中包含学号"},
137-
"name_field": {"name": "姓名", "description": "是否在播报中包含姓名"},
138-
"prefix_field": {"name": "播报前缀", "description": "在播报内容前添加的文本"},
139-
"suffix_field": {"name": "播报后缀", "description": "在播报内容后添加的文本"},
136+
"id_field": {"name": "学号"},
137+
"name_field": {"name": "姓名"},
138+
"prefix_field": {
139+
"name": "播报前缀",
140+
"description": "在播报内容前添加自定义文本",
141+
},
142+
"suffix_field": {
143+
"name": "播报后缀",
144+
"description": "在播报内容后添加自定义文本",
145+
},
140146
"lottery_title": {
141147
"name": "抽奖模式配置",
142148
"description": "配置抽奖模式下的语音播报",
143149
},
144-
"lottery_id_field": {"name": "序号", "description": "是否在播报中包含序号"},
145-
"lottery_name_field": {
146-
"name": "名称",
147-
"description": "是否在播报中包含名称",
148-
},
150+
"lottery_id_field": {"name": "序号"},
151+
"lottery_name_field": {"name": "名称"},
149152
"lottery_prefix_field": {
150153
"name": "抽奖前缀",
151-
"description": "在播报内容前添加的文本",
154+
"description": "在抽奖播报内容前添加自定义文本",
152155
},
153156
"lottery_suffix_field": {
154157
"name": "抽奖后缀",
155-
"description": "在播报内容后添加的文本",
158+
"description": "在抽奖播报内容后添加自定义文本",
156159
},
157160
"tts_alias": {
158161
"name": "替换名称",

app/common/voice/voice.py

Lines changed: 95 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

3333
# --------- 项目内部 ---------
3434
from app.tools.path_utils import ensure_dir, get_audio_path
35+
from app.tools.settings_access import readme_settings_async
36+
from app.tools.config import restore_volume
3537

3638

3739
# 权限检查装饰器
@@ -175,13 +177,13 @@ def _safe_play(self, data: np.ndarray, fs: int) -> None:
175177

176178
with self._is_playing_lock:
177179
self._is_playing = True # 开始播放
178-
logger.debug("开始播放音频")
180+
logger.info("开始播放音频")
179181

180182
# 分块写入避免卡顿
181183
chunk_size: int = 4096
182184
for i in range(0, len(data), chunk_size):
183185
if self._stop_flag.is_set():
184-
logger.debug("收到停止信号,中断播放")
186+
logger.info("收到停止信号,中断播放")
185187
break
186188

187189
chunk = data[i : i + chunk_size]
@@ -193,7 +195,7 @@ def _safe_play(self, data: np.ndarray, fs: int) -> None:
193195
# 写入音频流
194196
stream.write(chunk)
195197

196-
logger.debug("音频播放完毕")
198+
logger.info("音频播放完毕")
197199
with self._is_playing_lock:
198200
self._is_playing = False # 播放结束
199201

@@ -303,7 +305,7 @@ def get_voice(self, text: str, voice: str) -> Tuple[np.ndarray, int]:
303305
logger.error(f"无效的语音名称: {voice}")
304306
raise ValueError("语音名称不能为空")
305307

306-
logger.debug(f"获取语音: text='{text[:50]}...', voice='{voice}'")
308+
logger.info(f"获取语音: text='{text}', voice='{voice}'")
307309

308310
# 检查并执行缓存清理
309311
self._check_and_cleanup()
@@ -349,7 +351,7 @@ def get_voice(self, text: str, voice: str) -> Tuple[np.ndarray, int]:
349351
async def _generate_voice(self, text: str, voice: str) -> Tuple[np.ndarray, int]:
350352
"""生成语音核心方法"""
351353
# 限制文本长度,防止生成过大的音频
352-
max_text_length = 1000
354+
max_text_length = 500
353355
if len(text) > max_text_length:
354356
text = text[:max_text_length]
355357
logger.warning(f"文本长度超过限制{max_text_length},已截断")
@@ -464,32 +466,33 @@ class LoadBalancer:
464466

465467
# 基础队列大小设置
466468
BASE_QUEUE_SIZE: int = 3 # 基础队列大小(最低3人)
467-
468-
# CPU负载阈值与对应的队列大小增量
469-
CPU_THRESHOLDS: list[tuple[float, int]] = [
470-
(90, 0), # CPU > 90%: 不增加
471-
(80, 2), # 80% < CPU ≤ 90%: 增加2人
472-
(70, 4), # 70% < CPU ≤ 80%: 增加4人
473-
(60, 6), # 60% < CPU ≤ 70%: 增加6人
474-
(50, 8), # 50% < CPU ≤ 60%: 增加8人
475-
(40, 10), # 40% < CPU ≤ 50%: 增加10人
476-
(30, 12), # 30% < CPU ≤ 40%: 增加12人
477-
(20, 14), # 20% < CPU ≤ 30%: 增加14人
478-
(10, 16), # 10% < CPU ≤ 20%: 增加16人
479-
(0, 20), # CPU ≤ 10%: 增加20人
469+
MAX_QUEUE_SIZE: int = 100 # 最大队列大小,防止无限增长
470+
471+
# CPU负载阈值与对应的队列大小系数(按CPU使用率从低到高排序)
472+
CPU_THRESHOLDS: list[tuple[float, float]] = [
473+
(0, 1.0), # CPU ≤ 10%: 正常系数
474+
(10, 0.9), # 10% < CPU ≤ 20%: 略降系数
475+
(20, 0.8), # 20% < CPU ≤ 30%: 降低系数
476+
(30, 0.7), # 30% < CPU ≤ 40%: 进一步降低
477+
(40, 0.6), # 40% < CPU ≤ 50%: 继续降低
478+
(50, 0.5), # 50% < CPU ≤ 60%: 中等负载
479+
(60, 0.4), # 60% < CPU ≤ 70%: 较高负载
480+
(70, 0.3), # 70% < CPU ≤ 80%: 高负载
481+
(80, 0.2), # 80% < CPU ≤ 90%: 很高负载
482+
(90, 0.1), # CPU > 90%: 极高负载
480483
]
481484

482-
# 内存负载阈值与对应的队列大小增量
483-
MEMORY_THRESHOLDS: list[tuple[float, int]] = [
484-
(0.5, 0), # 内存 < 0.5GB: 不增加
485-
(1, 5), # 0.5GB ≤ 内存 < 1GB: 增加5人
486-
(2, 10), # 1GB ≤ 内存 < 2GB: 增加10人
487-
(4, 20), # 2GB ≤ 内存 < 4GB: 增加20人
488-
(8, 30), # 4GB ≤ 内存 < 8GB: 增加30人
489-
(16, 40), # 8GB ≤ 内存 < 16GB: 增加40人
490-
(32, 50), # 16GB ≤ 内存 < 32GB: 增加50人
491-
(64, 60), # 32GB ≤ 内存 < 64GB: 增加60人
492-
(float("inf"), 70), # 内存 ≥ 64GB: 增加70人
485+
# 内存负载阈值与对应的队列大小系数(按可用内存从低到高排序)
486+
MEMORY_THRESHOLDS: list[tuple[float, float]] = [
487+
(0, 0.2), # 内存 < 0.5GB: 极低内存,大幅降低队列
488+
(0.5, 0.3), # 0.5GB ≤ 内存 < 1GB: 低内存,降低队列
489+
(1, 0.5), # 1GB ≤ 内存 < 2GB: 中等偏低内存,适当降低
490+
(2, 0.7), # 2GB ≤ 内存 < 4GB: 中等内存,略微降低
491+
(4, 0.8), # 4GB ≤ 内存 < 8GB: 较充足内存,小幅降低
492+
(8, 0.9), # 8GB ≤ 内存 < 16GB: 充足内存,略降
493+
(16, 0.95), # 16GB ≤ 内存 < 32GB: 很充足内存,微调
494+
(32, 1.0), # 32GB ≤ 内存 < 64GB: 充足内存,正常系数
495+
(64, 1.0), # 内存 ≥ 64GB: 非常充足内存,正常系数
493496
]
494497

495498
def get_optimal_queue_size(self) -> int:
@@ -500,6 +503,7 @@ def get_optimal_queue_size(self) -> int:
500503
mem_available: float = psutil.virtual_memory().available / (
501504
1024**3
502505
) # GB(可用内存)
506+
mem_percent: float = psutil.virtual_memory().percent # 内存使用率%
503507

504508
# 参数有效性检查
505509
if (
@@ -514,25 +518,40 @@ def get_optimal_queue_size(self) -> int:
514518
logger.error("内存信息异常,使用基础队列大小")
515519
return self.BASE_QUEUE_SIZE
516520

517-
# 根据CPU使用率确定增量
518-
cpu_bonus: int = 0
519-
for threshold, bonus in self.CPU_THRESHOLDS:
520-
if cpu_percent >= threshold:
521-
cpu_bonus = bonus
521+
# 计算基于CPU的队列大小调整系数
522+
cpu_factor: float = 1.0
523+
for threshold, factor in self.CPU_THRESHOLDS:
524+
if cpu_percent > threshold:
525+
cpu_factor = factor
526+
else:
522527
break
523528

524-
# 根据可用内存确定增量
525-
mem_bonus: int = 0
526-
for threshold, bonus in self.MEMORY_THRESHOLDS:
527-
if mem_available <= threshold:
528-
mem_bonus = bonus
529+
# 计算基于内存的队列大小调整系数
530+
mem_factor: float = 1.0
531+
for threshold, factor in self.MEMORY_THRESHOLDS:
532+
if mem_available > threshold:
533+
mem_factor = factor
534+
else:
529535
break
530536

531-
# 计算最终队列大小
532-
queue_size: int = self.BASE_QUEUE_SIZE + cpu_bonus + mem_bonus
537+
# 计算动态队列基础值(根据系统资源情况)
538+
# 可用内存越多,动态基础值越大
539+
dynamic_base = self.BASE_QUEUE_SIZE + int(
540+
mem_available * 5
541+
) # 每GB内存增加5个队列位置
542+
543+
# 结合CPU和内存负载,计算最终队列大小
544+
# 使用几何平均计算综合系数,更合理地平衡CPU和内存负载
545+
combined_factor = (cpu_factor * mem_factor) ** 0.5
546+
queue_size: int = int(dynamic_base * combined_factor)
547+
548+
# 确保队列大小在合理范围内
549+
queue_size = max(self.BASE_QUEUE_SIZE, min(queue_size, self.MAX_QUEUE_SIZE))
533550

534551
logger.debug(
535-
f"系统负载 (CPU:{cpu_percent}%, 内存:{mem_available:.2f}GB),队列大小设为{queue_size}"
552+
f"系统负载 (CPU:{cpu_percent}%, 内存使用率:{mem_percent}%, 可用内存:{mem_available:.2f}GB) "
553+
f"→ CPU系数:{cpu_factor}, 内存系数:{mem_factor}, 综合系数:{combined_factor:.2f} "
554+
f"→ 队列大小设为{queue_size}"
536555
)
537556
return queue_size
538557
except Exception as e:
@@ -718,6 +737,23 @@ def _handle_system_tts(
718737
self, student_names: List[str], config: Dict[str, Any]
719738
) -> None:
720739
"""系统TTS处理"""
740+
# 检查是否开启系统音量控制(增强错误处理,防止崩溃)
741+
try:
742+
system_volume_control = readme_settings_async(
743+
"basic_voice_settings", "system_volume_control"
744+
)
745+
if system_volume_control:
746+
# 获取系统音量大小并设置
747+
system_volume_size = readme_settings_async(
748+
"basic_voice_settings", "system_volume_size"
749+
)
750+
# 确保音量值是整数
751+
if isinstance(system_volume_size, (int, str)):
752+
restore_volume(int(system_volume_size))
753+
except Exception as e:
754+
logger.error(f"系统音量控制处理失败: {e}")
755+
# 继续执行,不影响语音播报
756+
721757
with self.system_tts_lock:
722758
if self.voice_engine is None:
723759
logger.error("系统TTS引擎未初始化,无法播放语音")
@@ -769,6 +805,23 @@ def _prepare_and_play(
769805
self, student_names: List[str], config: Dict[str, Any], voice_name: str
770806
) -> None:
771807
"""准备并播放语音"""
808+
# 检查是否开启系统音量控制(增强错误处理,防止崩溃)
809+
try:
810+
system_volume_control = readme_settings_async(
811+
"basic_voice_settings", "system_volume_control"
812+
)
813+
if system_volume_control:
814+
# 获取系统音量大小并设置
815+
system_volume_size = readme_settings_async(
816+
"basic_voice_settings", "system_volume_size"
817+
)
818+
# 确保音量值是整数
819+
if isinstance(system_volume_size, (int, str)):
820+
restore_volume(int(system_volume_size))
821+
except Exception as e:
822+
logger.error(f"系统音量控制处理失败: {e}")
823+
# 继续执行,不影响语音播报
824+
772825
# 设置播放音量,转换为0.0-1.0范围
773826
self.playback_system.set_volume(config["voice_volume"] / 100.0)
774827
# 设置播放语速

0 commit comments

Comments
 (0)