豆包大模型,名称赋予
音频识别asr:使用本地方案funasr(复用的Fay项目中的funasr) FunASR服务连接测试脚本 用于验证本地FunASR WebSocket服务是否可以正常连接 webrtcapichat.html中对话框做进一步调整,侧边栏增加对话框的透明度调整。暂时设置对话框的背景色差异大些,美学设计暂不考虑。对话框支持隐藏
Showing
6 changed files
with
747 additions
and
0 deletions
web/asr/ali_nls.py
0 → 100644
| 1 | +from threading import Thread | ||
| 2 | +from threading import Lock | ||
| 3 | +import websocket | ||
| 4 | +import json | ||
| 5 | +import time | ||
| 6 | +import ssl | ||
| 7 | +import wave | ||
| 8 | +import _thread as thread | ||
| 9 | +from aliyunsdkcore.client import AcsClient | ||
| 10 | +from aliyunsdkcore.request import CommonRequest | ||
| 11 | + | ||
| 12 | +from core import wsa_server | ||
| 13 | +from scheduler.thread_manager import MyThread | ||
| 14 | +from utils import util | ||
| 15 | +from utils import config_util as cfg | ||
| 16 | +from core.authorize_tb import Authorize_Tb | ||
| 17 | + | ||
| 18 | +__running = True | ||
| 19 | +__my_thread = None | ||
| 20 | +_token = '' | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +def __post_token(): | ||
| 24 | + global _token | ||
| 25 | + __client = AcsClient( | ||
| 26 | + cfg.key_ali_nls_key_id, | ||
| 27 | + cfg.key_ali_nls_key_secret, | ||
| 28 | + "cn-shanghai" | ||
| 29 | + ) | ||
| 30 | + | ||
| 31 | + __request = CommonRequest() | ||
| 32 | + __request.set_method('POST') | ||
| 33 | + __request.set_domain('nls-meta.cn-shanghai.aliyuncs.com') | ||
| 34 | + __request.set_version('2019-02-28') | ||
| 35 | + __request.set_action_name('CreateToken') | ||
| 36 | + info = json.loads(__client.do_action_with_exception(__request)) | ||
| 37 | + _token = info['Token']['Id'] | ||
| 38 | + authorize = Authorize_Tb() | ||
| 39 | + authorize_info = authorize.find_by_userid(cfg.key_ali_nls_key_id) | ||
| 40 | + if authorize_info is not None: | ||
| 41 | + authorize.update_by_userid(cfg.key_ali_nls_key_id, _token, info['Token']['ExpireTime']*1000) | ||
| 42 | + else: | ||
| 43 | + authorize.add(cfg.key_ali_nls_key_id, _token, info['Token']['ExpireTime']*1000) | ||
| 44 | + | ||
| 45 | +def __runnable(): | ||
| 46 | + while __running: | ||
| 47 | + __post_token() | ||
| 48 | + time.sleep(60 * 60 * 12) | ||
| 49 | + | ||
| 50 | + | ||
| 51 | +def start(): | ||
| 52 | + MyThread(target=__runnable).start() | ||
| 53 | + | ||
| 54 | + | ||
| 55 | +class ALiNls: | ||
| 56 | + # 初始化 | ||
| 57 | + def __init__(self, username): | ||
| 58 | + self.__URL = 'wss://nls-gateway-cn-shenzhen.aliyuncs.com/ws/v1' | ||
| 59 | + self.__ws = None | ||
| 60 | + self.__frames = [] | ||
| 61 | + self.started = False | ||
| 62 | + self.__closing = False | ||
| 63 | + self.__task_id = '' | ||
| 64 | + self.done = False | ||
| 65 | + self.finalResults = "" | ||
| 66 | + self.username = username | ||
| 67 | + self.data = b'' | ||
| 68 | + self.__endding = False | ||
| 69 | + self.__is_close = False | ||
| 70 | + self.lock = Lock() | ||
| 71 | + | ||
| 72 | + def __create_header(self, name): | ||
| 73 | + if name == 'StartTranscription': | ||
| 74 | + self.__task_id = util.random_hex(32) | ||
| 75 | + header = { | ||
| 76 | + "appkey": cfg.key_ali_nls_app_key, | ||
| 77 | + "message_id": util.random_hex(32), | ||
| 78 | + "task_id": self.__task_id, | ||
| 79 | + "namespace": "SpeechTranscriber", | ||
| 80 | + "name": name | ||
| 81 | + } | ||
| 82 | + return header | ||
| 83 | + | ||
| 84 | + # 收到websocket消息的处理 | ||
| 85 | + def on_message(self, ws, message): | ||
| 86 | + try: | ||
| 87 | + data = json.loads(message) | ||
| 88 | + header = data['header'] | ||
| 89 | + name = header['name'] | ||
| 90 | + if name == 'TranscriptionStarted': | ||
| 91 | + self.started = True | ||
| 92 | + if name == 'SentenceEnd': | ||
| 93 | + self.done = True | ||
| 94 | + self.finalResults = data['payload']['result'] | ||
| 95 | + if wsa_server.get_web_instance().is_connected(self.username): | ||
| 96 | + wsa_server.get_web_instance().add_cmd({"panelMsg": self.finalResults, "Username" : self.username}) | ||
| 97 | + if wsa_server.get_instance().is_connected_human(self.username): | ||
| 98 | + content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': self.finalResults}, 'Username' : self.username} | ||
| 99 | + wsa_server.get_instance().add_cmd(content) | ||
| 100 | + ws.close()#TODO | ||
| 101 | + elif name == 'TranscriptionResultChanged': | ||
| 102 | + self.finalResults = data['payload']['result'] | ||
| 103 | + if wsa_server.get_web_instance().is_connected(self.username): | ||
| 104 | + wsa_server.get_web_instance().add_cmd({"panelMsg": self.finalResults, "Username" : self.username}) | ||
| 105 | + if wsa_server.get_instance().is_connected_human(self.username): | ||
| 106 | + content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': self.finalResults}, 'Username' : self.username} | ||
| 107 | + wsa_server.get_instance().add_cmd(content) | ||
| 108 | + | ||
| 109 | + except Exception as e: | ||
| 110 | + print(e) | ||
| 111 | + # print("### message:", message) | ||
| 112 | + | ||
| 113 | + # 收到websocket的关闭要求 | ||
| 114 | + def on_close(self, ws, code, msg): | ||
| 115 | + self.__endding = True | ||
| 116 | + self.__is_close = True | ||
| 117 | + | ||
| 118 | + # 收到websocket错误的处理 | ||
| 119 | + def on_error(self, ws, error): | ||
| 120 | + print("aliyun asr error:", error) | ||
| 121 | + self.started = True #避免在aliyun asr出错时,recorder一直等待start状态返回 | ||
| 122 | + | ||
| 123 | + # 收到websocket连接建立的处理 | ||
| 124 | + def on_open(self, ws): | ||
| 125 | + self.__endding = False | ||
| 126 | + #为了兼容多路asr,关闭过程数据 | ||
| 127 | + def run(*args): | ||
| 128 | + while self.__endding == False: | ||
| 129 | + try: | ||
| 130 | + if len(self.__frames) > 0: | ||
| 131 | + with self.lock: | ||
| 132 | + frame = self.__frames.pop(0) | ||
| 133 | + if isinstance(frame, dict): | ||
| 134 | + ws.send(json.dumps(frame)) | ||
| 135 | + elif isinstance(frame, bytes): | ||
| 136 | + ws.send(frame, websocket.ABNF.OPCODE_BINARY) | ||
| 137 | + self.data += frame | ||
| 138 | + else: | ||
| 139 | + time.sleep(0.001) # 避免忙等 | ||
| 140 | + except Exception as e: | ||
| 141 | + print(e) | ||
| 142 | + break | ||
| 143 | + if self.__is_close == False: | ||
| 144 | + for frame in self.__frames: | ||
| 145 | + ws.send(frame, websocket.ABNF.OPCODE_BINARY) | ||
| 146 | + frame = {"header": self.__create_header('StopTranscription')} | ||
| 147 | + ws.send(json.dumps(frame)) | ||
| 148 | + thread.start_new_thread(run, ()) | ||
| 149 | + | ||
| 150 | + def __connect(self): | ||
| 151 | + self.finalResults = "" | ||
| 152 | + self.done = False | ||
| 153 | + with self.lock: | ||
| 154 | + self.__frames.clear() | ||
| 155 | + self.__ws = websocket.WebSocketApp(self.__URL + '?token=' + _token, on_message=self.on_message) | ||
| 156 | + self.__ws.on_open = self.on_open | ||
| 157 | + self.__ws.on_error = self.on_error | ||
| 158 | + self.__ws.on_close = self.on_close | ||
| 159 | + self.__ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE}) | ||
| 160 | + | ||
| 161 | + def send(self, buf): | ||
| 162 | + with self.lock: | ||
| 163 | + self.__frames.append(buf) | ||
| 164 | + | ||
| 165 | + def start(self): | ||
| 166 | + Thread(target=self.__connect, args=[]).start() | ||
| 167 | + data = { | ||
| 168 | + 'header': self.__create_header('StartTranscription'), | ||
| 169 | + "payload": { | ||
| 170 | + "format": "pcm", | ||
| 171 | + "sample_rate": 16000, | ||
| 172 | + "enable_intermediate_result": True, | ||
| 173 | + "enable_punctuation_prediction": False, | ||
| 174 | + "enable_inverse_text_normalization": True, | ||
| 175 | + "speech_noise_threshold": -1 | ||
| 176 | + } | ||
| 177 | + } | ||
| 178 | + self.send(data) | ||
| 179 | + | ||
| 180 | + def end(self): | ||
| 181 | + self.__endding = True | ||
| 182 | + with wave.open('cache_data/input2.wav', 'wb') as wf: | ||
| 183 | + # 设置音频参数 | ||
| 184 | + n_channels = 1 # 单声道 | ||
| 185 | + sampwidth = 2 # 16 位音频,每个采样点 2 字节 | ||
| 186 | + wf.setnchannels(n_channels) | ||
| 187 | + wf.setsampwidth(sampwidth) | ||
| 188 | + wf.setframerate(16000) | ||
| 189 | + wf.writeframes(self.data) | ||
| 190 | + self.data = b'' |
web/asr/funasr.py
0 → 100644
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +""" | ||
| 3 | +AIfeng/2025-01-27 | ||
| 4 | +FunASR WebSocket客户端 - 兼容性包装器 | ||
| 5 | +基于新的FunASRClient实现 | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +import sys | ||
| 9 | +import os | ||
| 10 | + | ||
| 11 | +# 添加项目根目录到路径 | ||
| 12 | +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) | ||
| 13 | + | ||
| 14 | +from funasr_asr import FunASRClient | ||
| 15 | +# 修复导入路径 | ||
| 16 | +try: | ||
| 17 | + from core import wsa_server | ||
| 18 | +except ImportError: | ||
| 19 | + # 如果core模块不存在,创建一个模拟的wsa_server | ||
| 20 | + class MockWSAServer: | ||
| 21 | + def get_web_instance(self): | ||
| 22 | + return MockWebInstance() | ||
| 23 | + def get_instance(self): | ||
| 24 | + return MockInstance() | ||
| 25 | + | ||
| 26 | + class MockWebInstance: | ||
| 27 | + def is_connected(self, username): | ||
| 28 | + return False | ||
| 29 | + def add_cmd(self, cmd): | ||
| 30 | + print(f"Mock Web: {cmd}") | ||
| 31 | + | ||
| 32 | + class MockInstance: | ||
| 33 | + def is_connected_human(self, username): | ||
| 34 | + return False | ||
| 35 | + def add_cmd(self, cmd): | ||
| 36 | + print(f"Mock Human: {cmd}") | ||
| 37 | + | ||
| 38 | + wsa_server = MockWSAServer() | ||
| 39 | + | ||
| 40 | +try: | ||
| 41 | + from utils import config_util as cfg | ||
| 42 | +except ImportError: | ||
| 43 | + # 使用项目根目录的config_util | ||
| 44 | + import config_util as cfg | ||
| 45 | + | ||
| 46 | +try: | ||
| 47 | + from utils import util | ||
| 48 | +except ImportError: | ||
| 49 | + # 使用项目根目录的util | ||
| 50 | + import util | ||
| 51 | + | ||
| 52 | +class FunASR: | ||
| 53 | + """FunASR兼容性包装器""" | ||
| 54 | + | ||
| 55 | + def __init__(self, username): | ||
| 56 | + # 创建一个简单的选项对象 | ||
| 57 | + class SimpleOpt: | ||
| 58 | + def __init__(self, username): | ||
| 59 | + self.username = username | ||
| 60 | + | ||
| 61 | + opt = SimpleOpt(username) | ||
| 62 | + self.client = FunASRClient(opt) | ||
| 63 | + self.username = username | ||
| 64 | + self.__connected = False | ||
| 65 | + self.__frames = [] | ||
| 66 | + self.__state = 0 | ||
| 67 | + self.__closing = False | ||
| 68 | + self.__task_id = '' | ||
| 69 | + self.done = False | ||
| 70 | + self.finalResults = "" | ||
| 71 | + self.__reconnect_delay = 1 | ||
| 72 | + self.__reconnecting = False | ||
| 73 | + self.started = True | ||
| 74 | + | ||
| 75 | + # 消息处理回调 | ||
| 76 | + self.on_message_callback = None | ||
| 77 | + | ||
| 78 | + # 设置结果回调 | ||
| 79 | + self.client.set_result_callback(self._handle_result) | ||
| 80 | + | ||
| 81 | + def set_message_callback(self, callback): | ||
| 82 | + """设置消息回调函数""" | ||
| 83 | + self.on_message_callback = callback | ||
| 84 | + | ||
| 85 | + def _handle_result(self, message): | ||
| 86 | + """处理识别结果的内部方法""" | ||
| 87 | + try: | ||
| 88 | + self.done = True | ||
| 89 | + self.finalResults = message | ||
| 90 | + | ||
| 91 | + # 调用用户设置的回调函数 | ||
| 92 | + if self.on_message_callback: | ||
| 93 | + self.on_message_callback(message) | ||
| 94 | + | ||
| 95 | + if wsa_server.get_web_instance().is_connected(self.username): | ||
| 96 | + wsa_server.get_web_instance().add_cmd({"panelMsg": self.finalResults, "Username" : self.username}) | ||
| 97 | + if wsa_server.get_instance().is_connected_human(self.username): | ||
| 98 | + content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': self.finalResults}, 'Username' : self.username} | ||
| 99 | + wsa_server.get_instance().add_cmd(content) | ||
| 100 | + except Exception as e: | ||
| 101 | + print(e) | ||
| 102 | + | ||
| 103 | + # 兼容性方法 | ||
| 104 | + def on_message(self, ws, message): | ||
| 105 | + """兼容性方法 - 收到websocket消息的处理""" | ||
| 106 | + self._handle_result(message) | ||
| 107 | + | ||
| 108 | + def on_close(self, ws, code, msg): | ||
| 109 | + """兼容性方法 - 收到websocket错误的处理""" | ||
| 110 | + self.__connected = False | ||
| 111 | + # util.printInfo(1, self.username, f"### CLOSE:{msg}") | ||
| 112 | + | ||
| 113 | + def on_error(self, ws, error): | ||
| 114 | + """兼容性方法 - 收到websocket错误的处理""" | ||
| 115 | + self.__connected = False | ||
| 116 | + # util.printInfo(1, self.username, f"### error:{error}") | ||
| 117 | + | ||
| 118 | + def on_open(self, ws): | ||
| 119 | + """兼容性方法 - 收到websocket连接建立的处理""" | ||
| 120 | + self.__connected = True | ||
| 121 | + | ||
| 122 | + def add_frame(self, frame): | ||
| 123 | + """兼容性方法 - 添加音频帧""" | ||
| 124 | + if isinstance(frame, bytes): | ||
| 125 | + self.client.send_audio(frame) | ||
| 126 | + else: | ||
| 127 | + # 对于字典类型的控制消息,暂时忽略 | ||
| 128 | + pass | ||
| 129 | + | ||
| 130 | + def send(self, buf): | ||
| 131 | + """兼容性方法 - 发送音频数据""" | ||
| 132 | + if isinstance(buf, bytes): | ||
| 133 | + self.client.send_audio(buf) | ||
| 134 | + | ||
| 135 | + def send_url(self, url): | ||
| 136 | + """兼容性方法 - 发送URL(新客户端不支持此功能)""" | ||
| 137 | + print(f"警告: send_url功能在新的FunASR客户端中不支持: {url}") | ||
| 138 | + | ||
| 139 | + def start(self): | ||
| 140 | + """兼容性方法 - 启动识别""" | ||
| 141 | + self.client.start_recognition() | ||
| 142 | + self.__connected = True | ||
| 143 | + self.done = False | ||
| 144 | + self.finalResults = "" | ||
| 145 | + | ||
| 146 | + def end(self): | ||
| 147 | + """兼容性方法 - 结束识别""" | ||
| 148 | + self.client.stop_recognition() | ||
| 149 | + self.__closing = True | ||
| 150 | + self.__connected = False |
web/asr/funasr/ASR_client.py
0 → 100644
| 1 | +import pyaudio | ||
| 2 | +import websockets | ||
| 3 | +import asyncio | ||
| 4 | +from queue import Queue | ||
| 5 | +import argparse | ||
| 6 | +import json | ||
| 7 | + | ||
| 8 | +parser = argparse.ArgumentParser() | ||
| 9 | +parser.add_argument("--host", type=str, default="127.0.0.1", required=False, help="host ip, localhost, 0.0.0.0") | ||
| 10 | +parser.add_argument("--port", type=int, default=10197, required=False, help="grpc server port") | ||
| 11 | +parser.add_argument("--chunk_size", type=int, default=160, help="ms") | ||
| 12 | +parser.add_argument("--vad_needed", type=bool, default=True) | ||
| 13 | +args = parser.parse_args() | ||
| 14 | + | ||
| 15 | +voices = Queue() | ||
| 16 | + | ||
| 17 | +async def record(): | ||
| 18 | + global voices | ||
| 19 | + FORMAT = pyaudio.paInt16 | ||
| 20 | + CHANNELS = 1 | ||
| 21 | + RATE = 16000 | ||
| 22 | + CHUNK = int(RATE / 1000 * args.chunk_size) | ||
| 23 | + | ||
| 24 | + p = pyaudio.PyAudio() | ||
| 25 | + | ||
| 26 | + stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK) | ||
| 27 | + | ||
| 28 | + while True: | ||
| 29 | + data = stream.read(CHUNK) | ||
| 30 | + voices.put(data) | ||
| 31 | + await asyncio.sleep(0.01) | ||
| 32 | + | ||
| 33 | +async def ws_send(websocket): | ||
| 34 | + global voices | ||
| 35 | + print("Started sending data!") | ||
| 36 | + data_head = { | ||
| 37 | + 'vad_need': args.vad_needed, | ||
| 38 | + 'state': '' | ||
| 39 | + } | ||
| 40 | + await websocket.send(json.dumps(data_head)) | ||
| 41 | + | ||
| 42 | + while True: | ||
| 43 | + while not voices.empty(): | ||
| 44 | + data = voices.get() | ||
| 45 | + voices.task_done() | ||
| 46 | + try: | ||
| 47 | + await websocket.send(data) | ||
| 48 | + except Exception as e: | ||
| 49 | + print('Exception occurred:', e) | ||
| 50 | + return # Return to attempt reconnection | ||
| 51 | + await asyncio.sleep(0.01) | ||
| 52 | + | ||
| 53 | +async def message(websocket): | ||
| 54 | + while True: | ||
| 55 | + try: | ||
| 56 | + print(await websocket.recv()) | ||
| 57 | + except Exception as e: | ||
| 58 | + print("Exception:", e) | ||
| 59 | + return # Return to attempt reconnection | ||
| 60 | + | ||
| 61 | +async def ws_client(): | ||
| 62 | + uri = "ws://{}:{}".format(args.host, args.port) | ||
| 63 | + while True: | ||
| 64 | + try: | ||
| 65 | + async with websockets.connect(uri, subprotocols=["binary"], ping_interval=None) as websocket: | ||
| 66 | + task1 = asyncio.create_task(record()) | ||
| 67 | + task2 = asyncio.create_task(ws_send(websocket)) | ||
| 68 | + task3 = asyncio.create_task(message(websocket)) | ||
| 69 | + await asyncio.gather(task1, task2, task3) | ||
| 70 | + except Exception as e: | ||
| 71 | + print("WebSocket connection failed: ", e) | ||
| 72 | + await asyncio.sleep(5) # Wait for 5 seconds before trying to reconnect | ||
| 73 | + | ||
| 74 | +asyncio.get_event_loop().run_until_complete(ws_client()) |
web/asr/funasr/ASR_server.py
0 → 100644
| 1 | +import asyncio | ||
| 2 | +import websockets | ||
| 3 | +import argparse | ||
| 4 | +import json | ||
| 5 | +import logging | ||
| 6 | +from funasr import AutoModel | ||
| 7 | +import os | ||
| 8 | + | ||
| 9 | +# 设置日志级别 | ||
| 10 | +logger = logging.getLogger(__name__) | ||
| 11 | +logger.setLevel(logging.CRITICAL) | ||
| 12 | + | ||
| 13 | +# 解析命令行参数 | ||
| 14 | +parser = argparse.ArgumentParser() | ||
| 15 | +parser.add_argument("--host", type=str, default="0.0.0.0", help="host ip, localhost, 0.0.0.0") | ||
| 16 | +parser.add_argument("--port", type=int, default=10197, help="grpc server port") | ||
| 17 | +parser.add_argument("--ngpu", type=int, default=1, help="0 for cpu, 1 for gpu") | ||
| 18 | +parser.add_argument("--gpu_id", type=int, default=0, help="specify which gpu device to use") | ||
| 19 | +args = parser.parse_args() | ||
| 20 | + | ||
| 21 | +# 初始化模型 | ||
| 22 | +print("model loading") | ||
| 23 | +asr_model = AutoModel(model="paraformer-zh", model_revision="v2.0.4", | ||
| 24 | + vad_model="fsmn-vad", vad_model_revision="v2.0.4", | ||
| 25 | + punc_model="ct-punc-c", punc_model_revision="v2.0.4", | ||
| 26 | + device=f"cuda:{args.gpu_id}" if args.ngpu else "cpu", disable_update=True) | ||
| 27 | + # ,disable_update=True | ||
| 28 | +print("model loaded") | ||
| 29 | +websocket_users = {} | ||
| 30 | +task_queue = asyncio.Queue() | ||
| 31 | + | ||
| 32 | +async def ws_serve(websocket, path): | ||
| 33 | + global websocket_users | ||
| 34 | + user_id = id(websocket) | ||
| 35 | + websocket_users[user_id] = websocket | ||
| 36 | + try: | ||
| 37 | + async for message in websocket: | ||
| 38 | + if isinstance(message, str): | ||
| 39 | + data = json.loads(message) | ||
| 40 | + if 'url' in data: | ||
| 41 | + await task_queue.put((websocket, data['url'])) | ||
| 42 | + except websockets.exceptions.ConnectionClosed as e: | ||
| 43 | + logger.info(f"Connection closed: {e.reason}") | ||
| 44 | + except Exception as e: | ||
| 45 | + logger.error(f"Unexpected error: {e}") | ||
| 46 | + finally: | ||
| 47 | + logger.info(f"Cleaning up connection for user {user_id}") | ||
| 48 | + if user_id in websocket_users: | ||
| 49 | + del websocket_users[user_id] | ||
| 50 | + await websocket.close() | ||
| 51 | + logger.info("WebSocket closed") | ||
| 52 | + | ||
| 53 | +async def worker(): | ||
| 54 | + while True: | ||
| 55 | + websocket, url = await task_queue.get() | ||
| 56 | + if websocket.open: | ||
| 57 | + await process_wav_file(websocket, url) | ||
| 58 | + else: | ||
| 59 | + logger.info("WebSocket connection is already closed when trying to process file") | ||
| 60 | + task_queue.task_done() | ||
| 61 | + | ||
| 62 | +async def process_wav_file(websocket, url): | ||
| 63 | + # 热词 | ||
| 64 | + param_dict = {"sentence_timestamp": False} | ||
| 65 | + with open("data/hotword.txt", "r", encoding="utf-8") as f: | ||
| 66 | + lines = f.readlines() | ||
| 67 | + lines = [line.strip() for line in lines] | ||
| 68 | + hotword = " ".join(lines) | ||
| 69 | + print(f"热词:{hotword}") | ||
| 70 | + param_dict["hotword"] = hotword | ||
| 71 | + wav_path = url | ||
| 72 | + try: | ||
| 73 | + res = asr_model.generate(input=wav_path, is_final=True, **param_dict) | ||
| 74 | + if res: | ||
| 75 | + if 'text' in res[0] and websocket.open: | ||
| 76 | + await websocket.send(res[0]['text']) | ||
| 77 | + except Exception as e: | ||
| 78 | + print(f"Error during model.generate: {e}") | ||
| 79 | + finally: | ||
| 80 | + if os.path.exists(wav_path): | ||
| 81 | + os.remove(wav_path) | ||
| 82 | + | ||
| 83 | +async def main(): | ||
| 84 | + server = await websockets.serve(ws_serve, args.host, args.port, ping_interval=10) | ||
| 85 | + worker_task = asyncio.create_task(worker()) | ||
| 86 | + | ||
| 87 | + try: | ||
| 88 | + # 保持服务器运行,直到被手动中断 | ||
| 89 | + print(f"ASR服务器已启动,监听地址: {args.host}:{args.port}") | ||
| 90 | + await asyncio.Future() # 永久等待,直到程序被中断 | ||
| 91 | + except asyncio.CancelledError: | ||
| 92 | + print("服务器正在关闭...") | ||
| 93 | + finally: | ||
| 94 | + # 清理资源 | ||
| 95 | + worker_task.cancel() | ||
| 96 | + try: | ||
| 97 | + await worker_task | ||
| 98 | + except asyncio.CancelledError: | ||
| 99 | + pass | ||
| 100 | + server.close() | ||
| 101 | + await server.wait_closed() | ||
| 102 | + | ||
| 103 | +# 使用 asyncio 运行主函数 | ||
| 104 | +asyncio.run(main()) |
web/asr/funasr/README.md
0 → 100644
| 1 | +## 语音服务介绍 | ||
| 2 | + | ||
| 3 | +该服务以modelscope funasr语音识别为基础 | ||
| 4 | + | ||
| 5 | + | ||
| 6 | +## Install | ||
| 7 | +pip install torch -i https://mirrors.aliyun.com/pypi/simple/ | ||
| 8 | +pip install modelscope -i https://mirrors.aliyun.com/pypi/simple/ | ||
| 9 | +pip install testresources -i https://mirrors.aliyun.com/pypi/simple/ | ||
| 10 | +pip install websockets -i https://mirrors.aliyun.com/pypi/simple/ | ||
| 11 | +pip install torchaudio -i https://mirrors.aliyun.com/pypi/simple/ | ||
| 12 | +pip install FunASR -i https://mirrors.aliyun.com/pypi/simple/ | ||
| 13 | + | ||
| 14 | +## Start server | ||
| 15 | + | ||
| 16 | +2、python -u ASR_server.py --host "0.0.0.0" --port 10197 --ngpu 0 | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +## Fay connect | ||
| 21 | +更改fay/system.conf配置项,并重新启动fay. | ||
| 22 | + | ||
| 23 | +https://www.bilibili.com/video/BV1qs4y1g74e/?share_source=copy_web&vd_source=64cd9062f5046acba398177b62bea9ad | ||
| 24 | + | ||
| 25 | +## Acknowledge | ||
| 26 | +感谢 | ||
| 27 | +1. 中科大脑算法工程师张聪聪 | ||
| 28 | +2. [cgisky1980](https://github.com/cgisky1980/FunASR) | ||
| 29 | +3. [modelscope](https://github.com/modelscope/modelscope) | ||
| 30 | +4. [FunASR](https://github.com/alibaba-damo-academy/FunASR) | ||
| 31 | +5. [Fay数字人助理](https://github.com/TheRamU/Fay). | ||
| 32 | + | ||
| 33 | +-------------------------------------------------------------------------------------- | ||
| 34 | + | ||
| 35 | +GPU服务器部署: | ||
| 36 | +GPU服务器局域网地址:10.110.3.219 | ||
| 37 | +可用GPU:1号GPU | ||
| 38 | +python -u ASR_server.py --host "10.110.3.219" --port 10197 --ngpu 1 --gpu_id 1 | ||
| 39 | + | ||
| 40 | +python -u ASR_server.py --host "127.0.0.1" --port 10197 --ngpu 1 | ||
| 41 | + | ||
| 42 | +lsof -i :10197 | ||
| 43 | + | ||
| 44 | +#!/bin/bash | ||
| 45 | +source /home/fengyang/anaconda3/bin/activate livetalking | ||
| 46 | + | ||
| 47 | +nohup python /home/fengyang/controlPanel/main.py >> /home/fengyang/controlPanel/panel_logfile.log 2>&1 & | ||
| 48 | + | ||
| 49 | + | ||
| 50 | + | ||
| 51 | +chmod +x serverFunasr.sh |
web/asr/funasr/funasr_client_api.py
0 → 100644
| 1 | +''' | ||
| 2 | + Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights | ||
| 3 | + Reserved. MIT License (https://opensource.org/licenses/MIT) | ||
| 4 | + | ||
| 5 | + 2022-2023 by zhaomingwork@qq.com | ||
| 6 | +''' | ||
| 7 | +# pip install websocket-client | ||
| 8 | +import ssl | ||
| 9 | +from websocket import ABNF | ||
| 10 | +from websocket import create_connection | ||
| 11 | +from queue import Queue | ||
| 12 | +import threading | ||
| 13 | +import traceback | ||
| 14 | +import json | ||
| 15 | +import time | ||
| 16 | +import numpy as np | ||
| 17 | + | ||
| 18 | +import pyaudio | ||
| 19 | +import asyncio | ||
| 20 | +import argparse | ||
| 21 | + | ||
| 22 | +# class for recognizer in websocket | ||
| 23 | +class Funasr_websocket_recognizer(): | ||
| 24 | + ''' | ||
| 25 | + python asr recognizer lib | ||
| 26 | + | ||
| 27 | + ''' | ||
| 28 | + | ||
| 29 | + parser = argparse.ArgumentParser() | ||
| 30 | + parser.add_argument("--host", type=str, default="127.0.0.1", required=False, help="host ip, localhost, 0.0.0.0") | ||
| 31 | + parser.add_argument("--port", type=int, default=10194, required=False, help="grpc server port") | ||
| 32 | + parser.add_argument("--chunk_size", type=int, default=160, help="ms") | ||
| 33 | + parser.add_argument("--vad_needed", type=bool, default=True) | ||
| 34 | + args = parser.parse_args() | ||
| 35 | + | ||
| 36 | + def __init__(self, host="127.0.0.1", | ||
| 37 | + port="10197", | ||
| 38 | + is_ssl=True, | ||
| 39 | + chunk_size="0, 10, 5", | ||
| 40 | + chunk_interval=10, | ||
| 41 | + mode="2pass", | ||
| 42 | + wav_name="default"): | ||
| 43 | + ''' | ||
| 44 | + host: server host ip | ||
| 45 | + port: server port | ||
| 46 | + is_ssl: True for wss protocal, False for ws | ||
| 47 | + ''' | ||
| 48 | + try: | ||
| 49 | + if is_ssl == True: | ||
| 50 | + ssl_context = ssl.SSLContext() | ||
| 51 | + ssl_context.check_hostname = False | ||
| 52 | + ssl_context.verify_mode = ssl.CERT_NONE | ||
| 53 | + uri = "wss://{}:{}".format(host, port) | ||
| 54 | + ssl_opt={"cert_reqs": ssl.CERT_NONE} | ||
| 55 | + else: | ||
| 56 | + uri = "ws://{}:{}".format(host, port) | ||
| 57 | + ssl_context = None | ||
| 58 | + ssl_opt=None | ||
| 59 | + self.host = host | ||
| 60 | + self.port = port | ||
| 61 | + | ||
| 62 | + self.msg_queue = Queue() # used for recognized result text | ||
| 63 | + | ||
| 64 | + print("connect to url",uri) | ||
| 65 | + self.websocket=create_connection(uri, ssl=ssl_context, sslopt=ssl_opt) | ||
| 66 | + | ||
| 67 | + self.thread_msg = threading.Thread(target=Funasr_websocket_recognizer.thread_rec_msg, args=(self,)) | ||
| 68 | + self.thread_msg.start() | ||
| 69 | + chunk_size = [int(x) for x in chunk_size.split(",")] | ||
| 70 | + stride = int(60 * chunk_size[1] / chunk_interval / 1000 * 16000 * 2) | ||
| 71 | + chunk_num = (len(audio_bytes) - 1) // stride + 1 | ||
| 72 | + | ||
| 73 | + message = json.dumps({"mode": mode, | ||
| 74 | + "chunk_size": chunk_size, | ||
| 75 | + "encoder_chunk_look_back": 4, | ||
| 76 | + "decoder_chunk_look_back": 1, | ||
| 77 | + "chunk_interval": chunk_interval, | ||
| 78 | + "wav_name": wav_name, | ||
| 79 | + "is_speaking": True}) | ||
| 80 | + | ||
| 81 | + self.websocket.send(message) | ||
| 82 | + | ||
| 83 | + print("send json",message) | ||
| 84 | + | ||
| 85 | + except Exception as e: | ||
| 86 | + print("Exception:", e) | ||
| 87 | + traceback.print_exc() | ||
| 88 | + | ||
| 89 | + # async def record(): | ||
| 90 | + # global voices | ||
| 91 | + # FORMAT = pyaudio.paInt16 | ||
| 92 | + # CHANNELS = 1 | ||
| 93 | + # RATE = 16000 | ||
| 94 | + # CHUNK = int(RATE / 1000 * args.chunk_size) | ||
| 95 | + | ||
| 96 | + # p = pyaudio.PyAudio() | ||
| 97 | + | ||
| 98 | + # stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK) | ||
| 99 | + | ||
| 100 | + # while True: | ||
| 101 | + # data = stream.read(CHUNK) | ||
| 102 | + # voices.put(data) | ||
| 103 | + # await asyncio.sleep(0.01) | ||
| 104 | + | ||
| 105 | + | ||
| 106 | + # threads for rev msg | ||
| 107 | + def thread_rec_msg(self): | ||
| 108 | + try: | ||
| 109 | + while(True): | ||
| 110 | + msg=self.websocket.recv() | ||
| 111 | + if msg is None or len(msg) == 0: | ||
| 112 | + continue | ||
| 113 | + msg = json.loads(msg) | ||
| 114 | + | ||
| 115 | + self.msg_queue.put(msg) | ||
| 116 | + except Exception as e: | ||
| 117 | + print("client closed") | ||
| 118 | + | ||
| 119 | + # feed data to asr engine, wait_time means waiting for result until time out | ||
| 120 | + def feed_chunk(self, chunk, wait_time=0.01): | ||
| 121 | + try: | ||
| 122 | + self.websocket.send(chunk, ABNF.OPCODE_BINARY) | ||
| 123 | + # loop to check if there is a message, timeout in 0.01s | ||
| 124 | + while(True): | ||
| 125 | + msg = self.msg_queue.get(timeout=wait_time) | ||
| 126 | + if self.msg_queue.empty(): | ||
| 127 | + break | ||
| 128 | + | ||
| 129 | + return msg | ||
| 130 | + except: | ||
| 131 | + return "" | ||
| 132 | + | ||
| 133 | + def close(self,timeout=1): | ||
| 134 | + message = json.dumps({"is_speaking": False}) | ||
| 135 | + self.websocket.send(message) | ||
| 136 | + # sleep for timeout seconds to wait for result | ||
| 137 | + time.sleep(timeout) | ||
| 138 | + msg="" | ||
| 139 | + while(not self.msg_queue.empty()): | ||
| 140 | + msg = self.msg_queue.get() | ||
| 141 | + | ||
| 142 | + self.websocket.close() | ||
| 143 | + # only resturn the last msg | ||
| 144 | + return msg | ||
| 145 | + | ||
| 146 | +if __name__ == '__main__': | ||
| 147 | + | ||
| 148 | + print('example for Funasr_websocket_recognizer') | ||
| 149 | + import wave | ||
| 150 | + wav_path = "long.wav" | ||
| 151 | + # wav_path = "/Users/zhifu/Downloads/modelscope_models/speech_seaco_paraformer_large_asr_nat-zh-cn-16k-common-vocab8404-pytorch/example/asr_example.wav" | ||
| 152 | + with wave.open(wav_path, "rb") as wav_file: | ||
| 153 | + params = wav_file.getparams() | ||
| 154 | + frames = wav_file.readframes(wav_file.getnframes()) | ||
| 155 | + audio_bytes = bytes(frames) | ||
| 156 | + | ||
| 157 | + | ||
| 158 | + stride = int(60 * 10 / 10 / 1000 * 16000 * 2) | ||
| 159 | + chunk_num = (len(audio_bytes) - 1) // stride + 1 | ||
| 160 | + # create an recognizer | ||
| 161 | + rcg = Funasr_websocket_recognizer() | ||
| 162 | + # loop to send chunk | ||
| 163 | + for i in range(chunk_num): | ||
| 164 | + | ||
| 165 | + beg = i * stride | ||
| 166 | + data = audio_bytes[beg:beg + stride] | ||
| 167 | + | ||
| 168 | + text = rcg.feed_chunk(data,wait_time=0.02) | ||
| 169 | + if len(text)>0: | ||
| 170 | + print("text",text) | ||
| 171 | + time.sleep(0.05) | ||
| 172 | + | ||
| 173 | + # get last message | ||
| 174 | + text = rcg.close(timeout=3) | ||
| 175 | + print("text",text) | ||
| 176 | + | ||
| 177 | + | ||
| 178 | + |
-
Please register or login to post a comment