add:asr赋值audio_format

This commit is contained in:
hrz
2025-05-08 11:11:28 +08:00
parent ba86b34a8c
commit c2e000f937
5 changed files with 72 additions and 55 deletions
@@ -28,7 +28,8 @@ async def handleHelloMessage(conn, msg_json):
format = audio_params.get("format") format = audio_params.get("format")
logger.bind(tag=TAG).info(f"客户端音频格式: {format}") logger.bind(tag=TAG).info(f"客户端音频格式: {format}")
conn.audio_format = format conn.audio_format = format
conn.welcome_msg['audio_params'] = audio_params conn.asr.set_audio_format(format)
conn.welcome_msg["audio_params"] = audio_params
await conn.websocket.send(json.dumps(conn.welcome_msg)) await conn.websocket.send(json.dumps(conn.welcome_msg))
@@ -20,62 +20,75 @@ from core.providers.asr.base import ASRProviderBase
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
class AccessToken: class AccessToken:
@staticmethod @staticmethod
def _encode_text(text): def _encode_text(text):
encoded_text = parse.quote_plus(text) encoded_text = parse.quote_plus(text)
return encoded_text.replace('+', '%20').replace('*', '%2A').replace('%7E', '~') return encoded_text.replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
@staticmethod @staticmethod
def _encode_dict(dic): def _encode_dict(dic):
keys = dic.keys() keys = dic.keys()
dic_sorted = [(key, dic[key]) for key in sorted(keys)] dic_sorted = [(key, dic[key]) for key in sorted(keys)]
encoded_text = parse.urlencode(dic_sorted) encoded_text = parse.urlencode(dic_sorted)
return encoded_text.replace('+', '%20').replace('*', '%2A').replace('%7E', '~') return encoded_text.replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
@staticmethod @staticmethod
def create_token(access_key_id, access_key_secret): def create_token(access_key_id, access_key_secret):
parameters = {'AccessKeyId': access_key_id, parameters = {
'Action': 'CreateToken', "AccessKeyId": access_key_id,
'Format': 'JSON', "Action": "CreateToken",
'RegionId': 'cn-shanghai', "Format": "JSON",
'SignatureMethod': 'HMAC-SHA1', "RegionId": "cn-shanghai",
'SignatureNonce': str(uuid.uuid1()), "SignatureMethod": "HMAC-SHA1",
'SignatureVersion': '1.0', "SignatureNonce": str(uuid.uuid1()),
'Timestamp': time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "SignatureVersion": "1.0",
'Version': '2019-02-28'} "Timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"Version": "2019-02-28",
}
# 构造规范化的请求字符串 # 构造规范化的请求字符串
query_string = AccessToken._encode_dict(parameters) query_string = AccessToken._encode_dict(parameters)
# print('规范化的请求字符串: %s' % query_string) # print('规范化的请求字符串: %s' % query_string)
# 构造待签名字符串 # 构造待签名字符串
string_to_sign = 'GET' + '&' + AccessToken._encode_text('/') + '&' + AccessToken._encode_text(query_string) string_to_sign = (
"GET"
+ "&"
+ AccessToken._encode_text("/")
+ "&"
+ AccessToken._encode_text(query_string)
)
# print('待签名的字符串: %s' % string_to_sign) # print('待签名的字符串: %s' % string_to_sign)
# 计算签名 # 计算签名
secreted_string = hmac.new(bytes(access_key_secret + '&', encoding='utf-8'), secreted_string = hmac.new(
bytes(string_to_sign, encoding='utf-8'), bytes(access_key_secret + "&", encoding="utf-8"),
hashlib.sha1).digest() bytes(string_to_sign, encoding="utf-8"),
hashlib.sha1,
).digest()
signature = base64.b64encode(secreted_string) signature = base64.b64encode(secreted_string)
# print('签名: %s' % signature) # print('签名: %s' % signature)
# 进行URL编码 # 进行URL编码
signature = AccessToken._encode_text(signature) signature = AccessToken._encode_text(signature)
# print('URL编码后的签名: %s' % signature) # print('URL编码后的签名: %s' % signature)
# 调用服务 # 调用服务
full_url = 'http://nls-meta.cn-shanghai.aliyuncs.com/?Signature=%s&%s' % (signature, query_string) full_url = "http://nls-meta.cn-shanghai.aliyuncs.com/?Signature=%s&%s" % (
signature,
query_string,
)
# print('url: %s' % full_url) # print('url: %s' % full_url)
# 提交HTTP GET请求 # 提交HTTP GET请求
response = requests.get(full_url) response = requests.get(full_url)
if response.ok: if response.ok:
root_obj = response.json() root_obj = response.json()
key = 'Token' key = "Token"
if key in root_obj: if key in root_obj:
token = root_obj[key]['Id'] token = root_obj[key]["Id"]
expire_time = root_obj[key]['ExpireTime'] expire_time = root_obj[key]["ExpireTime"]
return token, expire_time return token, expire_time
# print(response.text) # print(response.text)
return None, None return None, None
class ASRProvider(ASRProviderBase): class ASRProvider(ASRProviderBase):
def __init__(self, config: dict, delete_audio_file: bool): def __init__(self, config: dict, delete_audio_file: bool):
"""阿里云ASR初始化""" """阿里云ASR初始化"""
@@ -102,28 +115,23 @@ class ASRProvider(ASRProviderBase):
# 确保输出目录存在 # 确保输出目录存在
os.makedirs(self.output_dir, exist_ok=True) os.makedirs(self.output_dir, exist_ok=True)
def _refresh_token(self): def _refresh_token(self):
"""刷新Token并记录过期时间""" """刷新Token并记录过期时间"""
if self.access_key_id and self.access_key_secret: if self.access_key_id and self.access_key_secret:
self.token, expire_time_str = AccessToken.create_token( self.token, expire_time_str = AccessToken.create_token(
self.access_key_id, self.access_key_id, self.access_key_secret
self.access_key_secret
) )
if not expire_time_str: if not expire_time_str:
raise ValueError("无法获取有效的Token过期时间") raise ValueError("无法获取有效的Token过期时间")
try: try:
#统一转换为字符串处理 # 统一转换为字符串处理
expire_str = str(expire_time_str).strip() expire_str = str(expire_time_str).strip()
if expire_str.isdigit(): if expire_str.isdigit():
expire_time = datetime.fromtimestamp(int(expire_str)) expire_time = datetime.fromtimestamp(int(expire_str))
else: else:
expire_time = datetime.strptime( expire_time = datetime.strptime(expire_str, "%Y-%m-%dT%H:%M:%SZ")
expire_str,
"%Y-%m-%dT%H:%M:%SZ"
)
self.expire_time = expire_time.timestamp() - 60 self.expire_time = expire_time.timestamp() - 60
except Exception as e: except Exception as e:
raise ValueError(f"无效的过期时间格式: {expire_str}") from e raise ValueError(f"无效的过期时间格式: {expire_str}") from e
@@ -145,9 +153,12 @@ class ASRProvider(ASRProviderBase):
# f"过期时间 {datetime.fromtimestamp(self.expire_time)} | " # f"过期时间 {datetime.fromtimestamp(self.expire_time)} | "
# f"剩余 {remaining:.2f}秒") # f"剩余 {remaining:.2f}秒")
return time.time() > self.expire_time return time.time() > self.expire_time
def generate_filename(self, extension=".wav"):
return os.path.join(self.output_file, f"tts-{__name__}{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
def generate_filename(self, extension=".wav"):
return os.path.join(
self.output_file,
f"tts-{__name__}{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
)
def _construct_request_url(self) -> str: def _construct_request_url(self) -> str:
"""构造请求URL,包含参数""" """构造请求URL,包含参数"""
@@ -159,7 +170,6 @@ class ASRProvider(ASRProviderBase):
request += "&enable_voice_detection=false" request += "&enable_voice_detection=false"
return request return request
def save_audio_to_file(self, pcm_data: List[bytes], session_id: str) -> str: def save_audio_to_file(self, pcm_data: List[bytes], session_id: str) -> str:
"""PCM数据保存为WAV文件""" """PCM数据保存为WAV文件"""
module_name = __name__.split(".")[-1] module_name = __name__.split(".")[-1]
@@ -170,7 +180,7 @@ class ASRProvider(ASRProviderBase):
wf.setnchannels(1) # 单声道 wf.setnchannels(1) # 单声道
wf.setsampwidth(2) # 16-bit wf.setsampwidth(2) # 16-bit
wf.setframerate(self.sample_rate) wf.setframerate(self.sample_rate)
wf.writeframes(b''.join(pcm_data)) wf.writeframes(b"".join(pcm_data))
logger.bind(tag=TAG).debug(f"音频文件已保存至: {file_path}") logger.bind(tag=TAG).debug(f"音频文件已保存至: {file_path}")
return file_path return file_path
@@ -180,9 +190,9 @@ class ASRProvider(ASRProviderBase):
try: try:
# 设置HTTP头 # 设置HTTP头
headers = { headers = {
'X-NLS-Token': self.token, "X-NLS-Token": self.token,
'Content-type': 'application/octet-stream', "Content-type": "application/octet-stream",
'Content-Length': str(len(pcm_data)) "Content-Length": str(len(pcm_data)),
} }
# 创建连接并发送请求 # 创建连接并发送请求
@@ -190,12 +200,12 @@ class ASRProvider(ASRProviderBase):
request_url = self._construct_request_url() request_url = self._construct_request_url()
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
await loop.run_in_executor(None, lambda: conn.request( await loop.run_in_executor(
method='POST', None,
url=request_url, lambda: conn.request(
body=pcm_data, method="POST", url=request_url, body=pcm_data, headers=headers
headers=headers ),
)) )
# 获取响应 # 获取响应
response = await loop.run_in_executor(None, conn.getresponse) response = await loop.run_in_executor(None, conn.getresponse)
@@ -205,10 +215,10 @@ class ASRProvider(ASRProviderBase):
# 解析响应 # 解析响应
try: try:
body_json = json.loads(body) body_json = json.loads(body)
status = body_json.get('status') status = body_json.get("status")
if status == 20000000: if status == 20000000:
result = body_json.get('result', '') result = body_json.get("result", "")
logger.bind(tag=TAG).debug(f"ASR结果: {result}") logger.bind(tag=TAG).debug(f"ASR结果: {result}")
return result return result
else: else:
@@ -223,7 +233,9 @@ class ASRProvider(ASRProviderBase):
logger.bind(tag=TAG).error(f"ASR请求失败: {e}", exc_info=True) logger.bind(tag=TAG).error(f"ASR请求失败: {e}", exc_info=True)
return None return None
async def speech_to_text(self, opus_data: List[bytes], session_id: str) -> Tuple[Optional[str], Optional[str]]: async def speech_to_text(
self, opus_data: List[bytes], session_id: str
) -> Tuple[Optional[str], Optional[str]]:
"""将语音数据转换为文本""" """将语音数据转换为文本"""
if self._is_token_expired(): if self._is_token_expired():
logger.warning("Token已过期,正在自动刷新...") logger.warning("Token已过期,正在自动刷新...")
@@ -235,8 +247,8 @@ class ASRProvider(ASRProviderBase):
if self.audio_format == "pcm": if self.audio_format == "pcm":
pcm_data = opus_data pcm_data = opus_data
else: else:
pcm_data = self.decode_opus(opus_data, session_id) pcm_data = self.decode_opus(opus_data)
combined_pcm_data = b''.join(pcm_data) combined_pcm_data = b"".join(pcm_data)
# 判断是否保存为WAV文件 # 判断是否保存为WAV文件
if self.delete_audio_file: if self.delete_audio_file:
@@ -253,7 +253,7 @@ class ASRProvider(ASRProviderBase):
if self.audio_format == "pcm": if self.audio_format == "pcm":
pcm_data = opus_data pcm_data = opus_data
else: else:
pcm_data = self.decode_opus(opus_data, session_id) pcm_data = self.decode_opus(opus_data)
combined_pcm_data = b"".join(pcm_data) combined_pcm_data = b"".join(pcm_data)
# 判断是否保存为WAV文件 # 判断是否保存为WAV文件
@@ -44,7 +44,7 @@ class ASRProvider(ASRProviderBase):
model=self.model_dir, model=self.model_dir,
vad_kwargs={"max_single_segment_time": 30000}, vad_kwargs={"max_single_segment_time": 30000},
disable_update=True, disable_update=True,
hub="hf" hub="hf",
# device="cuda:0", # 启用GPU加速 # device="cuda:0", # 启用GPU加速
) )
@@ -62,7 +62,9 @@ class ASRProvider(ASRProviderBase):
return file_path return file_path
async def speech_to_text(self, opus_data: List[bytes], session_id: str) -> Tuple[Optional[str], Optional[str]]: async def speech_to_text(
self, opus_data: List[bytes], session_id: str
) -> Tuple[Optional[str], Optional[str]]:
"""语音转文本主处理逻辑""" """语音转文本主处理逻辑"""
file_path = None file_path = None
try: try:
@@ -70,7 +72,7 @@ class ASRProvider(ASRProviderBase):
if self.audio_format == "pcm": if self.audio_format == "pcm":
pcm_data = opus_data pcm_data = opus_data
else: else:
pcm_data = self.decode_opus(opus_data, session_id) pcm_data = self.decode_opus(opus_data)
combined_pcm_data = b"".join(pcm_data) combined_pcm_data = b"".join(pcm_data)
@@ -90,7 +92,9 @@ class ASRProvider(ASRProviderBase):
batch_size_s=60, batch_size_s=60,
) )
text = rich_transcription_postprocess(result[0]["text"]) text = rich_transcription_postprocess(result[0]["text"])
logger.bind(tag=TAG).debug(f"语音识别耗时: {time.time() - start_time:.3f}s | 结果: {text}") logger.bind(tag=TAG).debug(
f"语音识别耗时: {time.time() - start_time:.3f}s | 结果: {text}"
)
return text, file_path return text, file_path
@@ -132,7 +132,7 @@ class ASRProvider(ASRProviderBase):
if self.audio_format == "pcm": if self.audio_format == "pcm":
pcm_data = opus_data pcm_data = opus_data
else: else:
pcm_data = self.decode_opus(opus_data, session_id) pcm_data = self.decode_opus(opus_data)
file_path = self.save_audio_to_file(pcm_data, session_id) file_path = self.save_audio_to_file(pcm_data, session_id)
logger.bind(tag=TAG).debug( logger.bind(tag=TAG).debug(
f"音频文件保存耗时: {time.time() - start_time:.3f}s | 路径: {file_path}" f"音频文件保存耗时: {time.time() - start_time:.3f}s | 路径: {file_path}"