WIP: saving the optimizing
This commit is contained in:
parent
dbf26dfb36
commit
82f557a4d0
Binary file not shown.
@ -29,7 +29,7 @@
|
|||||||
"automatic_player_status": true,
|
"automatic_player_status": true,
|
||||||
"automatic_player_url": "http://127.0.0.1:6000",
|
"automatic_player_url": "http://127.0.0.1:6000",
|
||||||
"liveRoom": {
|
"liveRoom": {
|
||||||
"enabled": true,
|
"enabled": false,
|
||||||
"url": ""
|
"url": ""
|
||||||
},
|
},
|
||||||
"record": {
|
"record": {
|
||||||
|
|||||||
110
core/recorder.py
110
core/recorder.py
@ -16,8 +16,11 @@ import tempfile
|
|||||||
import wave
|
import wave
|
||||||
from core import fay_core
|
from core import fay_core
|
||||||
from core import interact
|
from core import interact
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
# 启动时间 (秒)
|
# 启动时间 (秒)
|
||||||
_ATTACK = 0.2
|
_ATTACK = 0.05 # 更快进入拾音,减少短唤醒词被漏掉的概率
|
||||||
|
|
||||||
# 释放时间 (秒)
|
# 释放时间 (秒)
|
||||||
_RELEASE = 0.7
|
_RELEASE = 0.7
|
||||||
@ -85,6 +88,31 @@ class Recorder:
|
|||||||
with fay_core.auto_play_lock:
|
with fay_core.auto_play_lock:
|
||||||
fay_core.can_auto_play = True
|
fay_core.can_auto_play = True
|
||||||
|
|
||||||
|
def _norm_asr_text(self, s: str) -> str:
|
||||||
|
"""
|
||||||
|
大厅场景:ASR 结果常见问题
|
||||||
|
- 句首有空格/标点/语气词(嗯、啊、这个...)
|
||||||
|
- 唤醒词后跟逗号、冒号等
|
||||||
|
所以需要做最小必要的规范化,避免 front 模式误判为“待唤醒”
|
||||||
|
"""
|
||||||
|
if not s:
|
||||||
|
return ""
|
||||||
|
s = s.strip()
|
||||||
|
# 去掉句首标点/空白
|
||||||
|
s = re.sub(r'^[\s,,。.!!?::、~~]+', '', s)
|
||||||
|
# 去掉句首常见语气词(按需可继续加)
|
||||||
|
s = re.sub(r'^(嗯|啊|呃|额|哦|唔|这个|那个)\s*', '', s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def _strip_wake_prefix(self, full_text: str, wake_word: str) -> str:
|
||||||
|
"""
|
||||||
|
去除前置唤醒词,只把“真正的问题”交给对话系统
|
||||||
|
例如:'小F,播放音乐' -> '播放音乐'
|
||||||
|
"""
|
||||||
|
rest = full_text[len(wake_word):]
|
||||||
|
# 吞掉唤醒词后的空白/标点
|
||||||
|
return rest.lstrip(" \t,,。.!!?::、~~")
|
||||||
|
|
||||||
def __waitingResult(self, iat: asrclient, audio_data):
|
def __waitingResult(self, iat: asrclient, audio_data):
|
||||||
self.processing = True
|
self.processing = True
|
||||||
t = time.time()
|
t = time.time()
|
||||||
@ -142,33 +170,82 @@ class Recorder:
|
|||||||
self.timer = threading.Timer(60, self.reset_wakeup_status) # 重设计时器为60秒
|
self.timer = threading.Timer(60, self.reset_wakeup_status) # 重设计时器为60秒
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
|
|
||||||
#前置唤醒词模式
|
# 前置唤醒词模式(大厅优化版)
|
||||||
elif cfg.config['source']['wake_word_type'] == 'front':
|
elif cfg.config['source']['wake_word_type'] == 'front':
|
||||||
wake_word = cfg.config['source']['wake_word']
|
wake_word = cfg.config['source']['wake_word']
|
||||||
wake_word_list = wake_word.split(',')
|
wake_word_list = [w.strip() for w in wake_word.split(',') if w.strip()]
|
||||||
|
|
||||||
|
raw_text = text
|
||||||
|
text2 = self._norm_asr_text(raw_text)
|
||||||
|
|
||||||
wake_up = False
|
wake_up = False
|
||||||
for word in wake_word_list:
|
matched_word = None
|
||||||
if text.startswith(word):
|
|
||||||
wake_up_word = word
|
# 1) 规范化后做“严格句首匹配”
|
||||||
|
for w in wake_word_list:
|
||||||
|
w2 = self._norm_asr_text(w)
|
||||||
|
if w2 and text2.startswith(w2):
|
||||||
wake_up = True
|
wake_up = True
|
||||||
|
matched_word = w2
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# 2) 容错:允许唤醒词出现在句首很短范围内(防止语气词未完全清掉)
|
||||||
|
# 注意:范围要小,避免大厅误唤醒
|
||||||
|
if not wake_up:
|
||||||
|
N = 4 # 建议 3~6,越大越容易误触发
|
||||||
|
head = text2[:N]
|
||||||
|
for w in wake_word_list:
|
||||||
|
w2 = self._norm_asr_text(w)
|
||||||
|
if w2 and w2 in head:
|
||||||
|
wake_up = True
|
||||||
|
matched_word = w2
|
||||||
|
# 从唤醒词出现的位置截断,确保 strip 正确
|
||||||
|
idx = text2.find(w2)
|
||||||
|
text2 = text2[idx:]
|
||||||
|
break
|
||||||
|
|
||||||
if wake_up:
|
if wake_up:
|
||||||
util.printInfo(1, self.username, "唤醒成功!")
|
util.printInfo(1, self.username, f"唤醒成功!(front:{matched_word})")
|
||||||
if wsa_server.get_web_instance().is_connected(self.username):
|
if wsa_server.get_web_instance().is_connected(self.username):
|
||||||
wsa_server.get_web_instance().add_cmd({"panelMsg": "唤醒成功!", "Username" : self.username , 'robot': f'http://{cfg.fay_url}:5000/robot/Listening.jpg'})
|
wsa_server.get_web_instance().add_cmd({
|
||||||
|
"panelMsg": "唤醒成功!",
|
||||||
|
"Username": self.username,
|
||||||
|
'robot': f'http://{cfg.fay_url}:5000/robot/Listening.jpg'
|
||||||
|
})
|
||||||
if wsa_server.get_instance().is_connected(self.username):
|
if wsa_server.get_instance().is_connected(self.username):
|
||||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': "唤醒成功!"}, 'Username' : self.username, 'robot': f'http://{cfg.fay_url}:5000/robot/Listening.jpg'}
|
content = {
|
||||||
|
'Topic': 'Unreal',
|
||||||
|
'Data': {'Key': 'log', 'Value': "唤醒成功!"},
|
||||||
|
'Username': self.username,
|
||||||
|
'robot': f'http://{cfg.fay_url}:5000/robot/Listening.jpg'
|
||||||
|
}
|
||||||
wsa_server.get_instance().add_cmd(content)
|
wsa_server.get_instance().add_cmd(content)
|
||||||
#去除唤醒词后语句
|
|
||||||
question = text#[len(wake_up_word):].lstrip()
|
# ✅ 关键:剥离唤醒词,把真正问题交给对话系统
|
||||||
|
question = self._strip_wake_prefix(text2, matched_word)
|
||||||
|
|
||||||
|
# 如果只说了唤醒词(或后面太短),给一句提示
|
||||||
|
if not question:
|
||||||
|
question = "在呢,你说?"
|
||||||
|
|
||||||
self.on_speaking(question)
|
self.on_speaking(question)
|
||||||
self.processing = False
|
self.processing = False
|
||||||
else:
|
else:
|
||||||
util.printInfo(1, self.username, "[!] 待唤醒!")
|
# ✅ 关键:打印原始识别和规范化后文本,现场好定位为何没匹配上
|
||||||
|
util.printInfo(1, self.username, f"[!] 待唤醒!(front) ASR='{raw_text}' norm='{text2}'")
|
||||||
if wsa_server.get_web_instance().is_connected(self.username):
|
if wsa_server.get_web_instance().is_connected(self.username):
|
||||||
wsa_server.get_web_instance().add_cmd({"panelMsg": "[!] 待唤醒!", "Username" : self.username , 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'})
|
wsa_server.get_web_instance().add_cmd({
|
||||||
|
"panelMsg": "[!] 待唤醒!",
|
||||||
|
"Username": self.username,
|
||||||
|
'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'
|
||||||
|
})
|
||||||
if wsa_server.get_instance().is_connected(self.username):
|
if wsa_server.get_instance().is_connected(self.username):
|
||||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': "[!] 待唤醒!"}, 'Username' : self.username, 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'}
|
content = {
|
||||||
|
'Topic': 'Unreal',
|
||||||
|
'Data': {'Key': 'log', 'Value': "[!] 待唤醒!"},
|
||||||
|
'Username': self.username,
|
||||||
|
'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'
|
||||||
|
}
|
||||||
wsa_server.get_instance().add_cmd(content)
|
wsa_server.get_instance().add_cmd(content)
|
||||||
|
|
||||||
#非唤醒模式
|
#非唤醒模式
|
||||||
@ -234,7 +311,10 @@ class Recorder:
|
|||||||
|
|
||||||
#计算音量是否满足激活拾音
|
#计算音量是否满足激活拾音
|
||||||
level = audioop.rms(data, 2)
|
level = audioop.rms(data, 2)
|
||||||
if len(self.__history_data) >= 10:#保存激活前的音频,以免信息掉失
|
|
||||||
|
# 把激活前缓存拉长,避免“唤醒词”在触发拾音前被漏掉
|
||||||
|
# 1024帧@16kHz≈64ms/块,30块≈1.9秒
|
||||||
|
if len(self.__history_data) >= 30: # 保存激活前的音频,以免信息掉失
|
||||||
self.__history_data.pop(0)
|
self.__history_data.pop(0)
|
||||||
if len(self.__history_level) >= 500:
|
if len(self.__history_level) >= 500:
|
||||||
self.__history_level.pop(0)
|
self.__history_level.pop(0)
|
||||||
|
|||||||
@ -150,6 +150,19 @@ class FayInterface {
|
|||||||
|
|
||||||
handleIncomingMessage(data) {
|
handleIncomingMessage(data) {
|
||||||
const vueInstance = this.vueInstance;
|
const vueInstance = this.vueInstance;
|
||||||
|
|
||||||
|
if (data.panelReply !== undefined) {
|
||||||
|
vueInstance.panelReply = data.panelReply.content;
|
||||||
|
|
||||||
|
// 发送消息给父窗口,并指定目标 origin(必须是父组件的域名)
|
||||||
|
if (window.parent) {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{type: 'panelReply', data: data.panelReply.content},
|
||||||
|
'*' // 父组件的域名
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// console.log('Incoming message:', data);
|
// console.log('Incoming message:', data);
|
||||||
if (data.liveState !== undefined) {
|
if (data.liveState !== undefined) {
|
||||||
vueInstance.liveState = data.liveState;
|
vueInstance.liveState = data.liveState;
|
||||||
|
|||||||
2
qa.csv
2
qa.csv
@ -1,4 +1,4 @@
|
|||||||
你好,你好,我是小橄榄!有什么我可以帮助你的吗
|
你好,你好,我是小橄榄!有什么我可以帮助你的吗
|
||||||
我们现在在哪里,我们现在在冕宁元升农业的展览厅,可以参观游览我们先进的油橄榄产业园区哦!
|
我们现在在哪里,我们现在在冕宁元升农业的展览厅,可以参观游览我们先进的油橄榄产业园区哦!
|
||||||
介绍一下基地,元升集团油橄榄种植基地是中国目前最大的油橄榄种植庄园,目前整个庄园面积已接近30000亩。
|
介绍一下基地,元升集团油橄榄种植基地是中国目前最大的油橄榄种植庄园,目前整个庄园面积已接近30000亩。
|
||||||
介绍一下元升集团,2011年,冕宁元升农业董事长林春福跟随周恩来总理的脚步,在冕宁地区开启了油橄榄庄园打造之路。经过十年的发展,冕宁元升农业目前已成为国家林业局示范基地、国家林业重点龙头企业、四川省第一种植庄园、四川省脱贫标杆企业,获得各种荣誉奖项200余项。
|
介绍一下元升集团,2011年,冕宁元升农业董事长林春福跟随周恩来总理的脚步,在冕宁地区开启了油橄榄庄园打造之路。经过十年的发展,冕宁元升农业目前已成为国家林业局示范基地、国家林业重点龙头企业、四川省第一种植庄园、四川省脱贫标杆企业,获得各种荣誉奖项200余项。
|
||||||
|
|||||||
|
@ -52,11 +52,11 @@ lingju_api_authcode=
|
|||||||
|
|
||||||
#gpt 服务密钥(NLP多选1) https://openai.com/
|
#gpt 服务密钥(NLP多选1) https://openai.com/
|
||||||
#免费key只支持gpt 3.5 ,若想使用其他model,可到 https://api.zyai.online/register/?aff_code=MyCI 下购买申请。
|
#免费key只支持gpt 3.5 ,若想使用其他model,可到 https://api.zyai.online/register/?aff_code=MyCI 下购买申请。
|
||||||
gpt_api_key=sk-4Spva89SGSikpacz3a70Dd081cA84c9a8dEd345f19C9BdFc
|
gpt_api_key=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
|
||||||
#gpt base url 如:https://api.openai.com/v1、https://rwkv.ai-creator.net/chntuned/v1、https://api.fastgpt.in/api/v1、https://api.moonshot.cn/v1
|
#gpt base url 如:https://api.openai.com/v1、https://rwkv.ai-creator.net/chntuned/v1、https://api.fastgpt.in/api/v1、https://api.moonshot.cn/v1
|
||||||
gpt_base_url=https://api.zyai.online/v1
|
gpt_base_url=https://openrouter.ai/api/v1
|
||||||
#gpt model engine 如:gpt-3.5-turbo、moonshot-v1-8k
|
#gpt model engine 如:gpt-3.5-turbo、moonshot-v1-8k
|
||||||
gpt_model_engine=gpt-3.5-turbo
|
gpt_model_engine=qwen/qwen3-4b:free
|
||||||
|
|
||||||
#gpt(fastgpt)代理(可为空,填写例子:127.0.0.1:7890)
|
#gpt(fastgpt)代理(可为空,填写例子:127.0.0.1:7890)
|
||||||
proxy_config=
|
proxy_config=
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user