From a557a55b8bdd2923f1b4a9b3e4e0ff402cc05aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=BE=E8=81=AA?= Date: Tue, 20 Jun 2023 10:25:53 +0800 Subject: [PATCH 1/4] update funasr-wss-client funasr-wss-server --- .../runtime/websocket/funasr-wss-client.cpp | 3 +- .../runtime/websocket/funasr-wss-server.cpp | 84 +++++++++++++++++-- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/funasr/runtime/websocket/funasr-wss-client.cpp b/funasr/runtime/websocket/funasr-wss-client.cpp index 8b59000fc..53301253c 100644 --- a/funasr/runtime/websocket/funasr-wss-client.cpp +++ b/funasr/runtime/websocket/funasr-wss-client.cpp @@ -277,13 +277,14 @@ class WebsocketClient { }; int main(int argc, char* argv[]) { + google::InitGoogleLogging(argv[0]); FLAGS_logtostderr = true; TCLAP::CmdLine cmd("funasr-ws-client", ' ', "1.0"); TCLAP::ValueArg server_ip_("", "server-ip", "server-ip", true, "127.0.0.1", "string"); - TCLAP::ValueArg port_("", "port", "port", true, "8889", "string"); + TCLAP::ValueArg port_("", "port", "port", true, "10095", "string"); TCLAP::ValueArg wav_path_("", "wav-path", "the input could be: wav_path, e.g.: asr_example.wav; pcm_path, e.g.: asr_example.pcm; wav.scp, kaldi style wav list (wav_id \t wav_path)", true, "", "string"); diff --git a/funasr/runtime/websocket/funasr-wss-server.cpp b/funasr/runtime/websocket/funasr-wss-server.cpp index 5f2af5ce0..a6fa2e137 100644 --- a/funasr/runtime/websocket/funasr-wss-server.cpp +++ b/funasr/runtime/websocket/funasr-wss-server.cpp @@ -11,6 +11,7 @@ // [--vad-quant ] [--vad-dir ] [--quantize // ] --model-dir [--] [--version] [-h] #include "websocket-server.h" +#include using namespace std; void GetValue(TCLAP::ValueArg& value_arg, string key, @@ -20,10 +21,15 @@ void GetValue(TCLAP::ValueArg& value_arg, string key, } int main(int argc, char* argv[]) { try { + google::InitGoogleLogging(argv[0]); FLAGS_logtostderr = true; TCLAP::CmdLine cmd("funasr-ws-server", ' ', "1.0"); + TCLAP::ValueArg download_model_dir( + "", "download-model-dir", + "Download model from Modelscope to download_model_dir", + false, "", "string"); TCLAP::ValueArg model_dir( "", MODEL_DIR, "default: /workspace/models/asr, the asr model path, which contains model.onnx, config.yaml, am.mvn", @@ -53,15 +59,15 @@ int main(int argc, char* argv[]) { "true, load the model of model_quant.onnx in punc_dir", false, "true", "string"); - TCLAP::ValueArg listen_ip("", "listen_ip", "listen_ip", false, + TCLAP::ValueArg listen_ip("", "listen-ip", "listen ip", false, "0.0.0.0", "string"); - TCLAP::ValueArg port("", "port", "port", false, 8889, "int"); - TCLAP::ValueArg io_thread_num("", "io_thread_num", "io_thread_num", + TCLAP::ValueArg port("", "port", "port", false, 10095, "int"); + TCLAP::ValueArg io_thread_num("", "io-thread-num", "io thread num", false, 8, "int"); TCLAP::ValueArg decoder_thread_num( - "", "decoder_thread_num", "decoder_thread_num", false, 8, "int"); - TCLAP::ValueArg model_thread_num("", "model_thread_num", - "model_thread_num", false, 1, "int"); + "", "decoder-thread-num", "decoder thread num", false, 8, "int"); + TCLAP::ValueArg model_thread_num("", "model-thread-num", + "model thread num", false, 1, "int"); TCLAP::ValueArg certfile("", "certfile", "default: ../../../ssl_key/server.crt, path of certficate for WSS connection. if it is empty, it will be in WS mode.", @@ -73,6 +79,7 @@ int main(int argc, char* argv[]) { cmd.add(certfile); cmd.add(keyfile); + cmd.add(download_model_dir); cmd.add(model_dir); cmd.add(quantize); cmd.add(vad_dir); @@ -95,6 +102,71 @@ int main(int argc, char* argv[]) { GetValue(punc_dir, PUNC_DIR, model_path); GetValue(punc_quant, PUNC_QUANT, model_path); + // Download model form Modelscope + try{ + std::string s_download_model_dir = download_model_dir.getValue(); + if(download_model_dir.isSet() && !s_download_model_dir.empty()){ + if (access(s_download_model_dir.c_str(), F_OK) != 0){ + LOG(ERROR) << s_download_model_dir << " do not exists."; + exit(-1); + } + std::string s_vad_path = model_path[VAD_DIR]; + std::string s_asr_path = model_path[MODEL_DIR]; + std::string s_punc_path = model_path[PUNC_DIR]; + std::string python_cmd = "python -m funasr.export.export_model --type onnx --quantize True "; + if(vad_dir.isSet() && !s_vad_path.empty()){ + std::string python_cmd_vad = python_cmd + " --model-name " + s_vad_path + " --export-dir " + s_download_model_dir; + LOG(INFO) << "Download model: " << s_vad_path << " from modelscope: "; + system(python_cmd_vad.c_str()); + std::string down_vad_path = s_download_model_dir+"/"+s_vad_path; + std::string down_vad_model = s_download_model_dir+"/"+s_vad_path+"/model_quant.onnx"; + if (access(down_vad_model.c_str(), F_OK) != 0){ + LOG(ERROR) << down_vad_model << " do not exists."; + exit(-1); + }else{ + model_path[VAD_DIR]=down_vad_path; + LOG(INFO) << "Set " << VAD_DIR << " : " << model_path[VAD_DIR]; + } + }else{ + LOG(INFO) << "VAD model is not set, use default."; + } + if(model_dir.isSet() && !s_asr_path.empty()){ + std::string python_cmd_asr = python_cmd + " --model-name " + s_asr_path + " --export-dir " + s_download_model_dir; + LOG(INFO) << "Download model: " << s_asr_path << " from modelscope: "; + system(python_cmd_asr.c_str()); + std::string down_asr_path = s_download_model_dir+"/"+s_asr_path; + std::string down_asr_model = s_download_model_dir+"/"+s_asr_path+"/model_quant.onnx"; + if (access(down_asr_model.c_str(), F_OK) != 0){ + LOG(ERROR) << down_asr_model << " do not exists."; + exit(-1); + }else{ + model_path[MODEL_DIR]=down_asr_path; + LOG(INFO) << "Set " << MODEL_DIR << " : " << model_path[MODEL_DIR]; + } + }else{ + LOG(INFO) << "ASR model is not set, use default."; + } + if(punc_dir.isSet() && !s_punc_path.empty()){ + std::string python_cmd_punc = python_cmd + " --model-name " + s_punc_path + " --export-dir " + s_download_model_dir; + LOG(INFO) << "Download model: " << s_punc_path << " from modelscope: "; + system(python_cmd_punc.c_str()); + std::string down_punc_path = s_download_model_dir+"/"+s_punc_path; + std::string down_punc_model = s_download_model_dir+"/"+s_punc_path+"/model_quant.onnx"; + if (access(down_punc_model.c_str(), F_OK) != 0){ + LOG(ERROR) << down_punc_model << " do not exists."; + exit(-1); + }else{ + model_path[PUNC_DIR]=down_punc_path; + LOG(INFO) << "Set " << PUNC_DIR << " : " << model_path[PUNC_DIR]; + } + }else{ + LOG(INFO) << "PUNC model is not set, use default."; + } + } + } catch (std::exception const& e) { + LOG(ERROR) << "Error: " << e.what(); + } + std::string s_listen_ip = listen_ip.getValue(); int s_port = port.getValue(); int s_io_thread_num = io_thread_num.getValue(); From 369f2ff8838569351c26d909407b497ab16d3bcf Mon Sep 17 00:00:00 2001 From: zhaomingwork <61895407+zhaomingwork@users.noreply.github.com> Date: Tue, 20 Jun 2023 11:08:50 +0800 Subject: [PATCH 2/4] add html5 local access asr service directly (#654) --- funasr/runtime/html5/readme.md | 14 +- funasr/runtime/html5/static/index.html | 25 ++- funasr/runtime/html5/static/main.js | 192 ++++++++++++++++++--- funasr/runtime/html5/static/wsconnecter.js | 7 +- 4 files changed, 207 insertions(+), 31 deletions(-) diff --git a/funasr/runtime/html5/readme.md b/funasr/runtime/html5/readme.md index 1e9031eec..0c1eba0ea 100644 --- a/funasr/runtime/html5/readme.md +++ b/funasr/runtime/html5/readme.md @@ -51,8 +51,18 @@ https://127.0.0.1:1337/static/index.html # https://30.220.136.139:1337/static/index.html ``` -### modify asr address in html according to your environment -asr address in index.html must be wss +### open browser to open html5 file directly without h5Server +you can run html5 client by just clicking the index.html file directly in your computer. +1) lauch asr service without ssl, it must be in ws mode as ssl protocol will prohibit such access. +2) copy whole directory /funasr/runtime/html5/static to your computer +3) open /funasr/runtime/html5/static/index.html by browser +4) enter asr service ws address and connect + + +```shell + +``` + ## Acknowledge diff --git a/funasr/runtime/html5/static/index.html b/funasr/runtime/html5/static/index.html index 99aa9b477..23d6fece3 100644 --- a/funasr/runtime/html5/static/index.html +++ b/funasr/runtime/html5/static/index.html @@ -21,14 +21,35 @@

+
+
+ 选择录音模式:
+ +    + + +
+
-
+
选择asr模型模式:
+       - + + +
+ +
+ + + 语音识别结果显示:
diff --git a/funasr/runtime/html5/static/main.js b/funasr/runtime/html5/static/main.js index 22f53c152..35e533a43 100644 --- a/funasr/runtime/html5/static/main.js +++ b/funasr/runtime/html5/static/main.js @@ -32,15 +32,121 @@ btnStart.disabled = true; btnConnect= document.getElementById('btnConnect'); btnConnect.onclick = start; -var rec_text=""; -var offline_text=""; +var rec_text=""; // for online rec asr result +var offline_text=""; // for offline rec asr result var info_div = document.getElementById('info_div'); -//var now_ipaddress=window.location.href; -//now_ipaddress=now_ipaddress.replace("https://","wss://"); -//now_ipaddress=now_ipaddress.replace("static/index.html",""); -//document.getElementById('wssip').value=now_ipaddress; +var upfile = document.getElementById('upfile'); + + +var isfilemode=false; // if it is in file mode +var file_data_array; // array to save file data +var isconnected=0; // for file rec, 0 is not begin, 1 is connected, -1 is error +var totalsend=0; + +upfile.onchange = function () { +      var len = this.files.length; + for(let i = 0; i < len; i++) { + let fileAudio = new FileReader(); + fileAudio.readAsArrayBuffer(this.files[i]); + fileAudio.onload = function() { + var audioblob= fileAudio.result; + file_data_array=audioblob; + console.log(audioblob); + btnConnect.disabled = false; + info_div.innerHTML='请点击连接进行识别'; + + } +          fileAudio.onerror = function(e) { +            console.log('error' + e); +          } + } + } + +function play_file() +{ + var audioblob=new Blob( [ new Uint8Array(file_data_array)] , {type :"audio/wav"}); + var audio_record = document.getElementById('audio_record'); + audio_record.src = (window.URL||webkitURL).createObjectURL(audioblob); + audio_record.controls=true; + audio_record.play(); +} +function start_file_send() +{ + sampleBuf=new Int16Array( file_data_array ); + + var chunk_size=960; // for asr chunk_size [5, 10, 5] + + + + + + while(sampleBuf.length>=chunk_size){ + + sendBuf=sampleBuf.slice(0,chunk_size); + totalsend=totalsend+sampleBuf.length; + sampleBuf=sampleBuf.slice(chunk_size,sampleBuf.length); + wsconnecter.wsSend(sendBuf,false); + + + } + + stop(); + + + +} +function start_file_offline() +{ + console.log("start_file_offline",isconnected); + if(isconnected==-1) + { + return; + } + if(isconnected==0){ + + setTimeout(start_file_offline, 1000); + return; + } + start_file_send(); + + + + +} + +function on_recoder_mode_change() +{ + var item = null; + var obj = document.getElementsByName("recoder_mode"); + for (var i = 0; i < obj.length; i++) { //遍历Radio + if (obj[i].checked) { + item = obj[i].value; + break; + } + + + } + if(item=="mic") + { + document.getElementById("mic_mode_div").style.display = 'block'; + document.getElementById("rec_mode_div").style.display = 'none'; + + btnConnect.disabled=false; + isfilemode=false; + } + else + { + document.getElementById("mic_mode_div").style.display = 'none'; + document.getElementById("rec_mode_div").style.display = 'block'; + btnConnect.disabled = true; + isfilemode=true; + info_div.innerHTML='请点击选择文件'; + + + } +} function getAsrMode(){ var item = null; @@ -53,7 +159,12 @@ function getAsrMode(){ } + if(isfilemode) + { + item= "offline"; + } console.log("asr mode"+item); + return item; } @@ -78,6 +189,18 @@ function getJsonMessage( jsonMsg ) { varArea.value=rec_text; console.log( "offline_text: " + asrmodel+","+offline_text); console.log( "rec_text: " + rec_text); + if (isfilemode==true){ + console.log("call stop ws!"); + play_file(); + wsconnecter.wsStop(); + + info_div.innerHTML="请点击连接"; + isconnected=0; + btnStart.disabled = true; + btnStop.disabled = true; + btnConnect.disabled=false; + } + } @@ -86,14 +209,11 @@ function getJsonMessage( jsonMsg ) { function getConnState( connState ) { if ( connState === 0 ) { - //rec.open( function(){ - // rec.start(); - // console.log("开始录音"); - //}); - btnStart.disabled = false; - btnConnect.disabled = true; info_div.innerHTML='连接成功!请点击开始'; + if (isfilemode==true){ + info_div.innerHTML='请耐心等待,大文件等待时间更长'; + } } else if ( connState === 1 ) { //stop(); } else if ( connState === 2 ) { @@ -102,36 +222,52 @@ function getConnState( connState ) { alert("连接地址"+document.getElementById('wssip').value+"失败,请检查asr地址和端口,并确保h5服务和asr服务在同一个域内。或换个浏览器试试。"); btnStart.disabled = true; - + isconnected=0; + info_div.innerHTML='请点击连接'; } } function record() { + rec.open( function(){ rec.start(); console.log("开始"); btnStart.disabled = true; }); + } + + + // 识别启动、停止、清空操作 function start() { // 清除显示 clear(); //控件状态更新 - + console.log("isfilemode"+isfilemode+","+isconnected); info_div.innerHTML="正在连接asr服务器,请等待..."; //启动连接 var ret=wsconnecter.wsStart(); if(ret==1){ isRec = true; - btnStart.disabled = true; + btnStart.disabled = false; btnStop.disabled = false; btnConnect.disabled=true; - + if (isfilemode) + { + console.log("start file now"); + start_file_offline(); + + btnStart.disabled = true; + btnStop.disabled = true; + btnConnect.disabled = true; + } + return 1; } + return 0; } @@ -152,21 +288,26 @@ function stop() { } wsconnecter.wsSend( JSON.stringify(request) ,false); + + - - - + //isconnected=0; // 控件状态更新 + isRec = false; - info_div.innerHTML="请等候..."; - btnStop.disabled = true; - setTimeout(function(){ - console.log("call stop ws!"); - wsconnecter.wsStop(); + info_div.innerHTML="发送完数据,请等候,正在识别..."; + + if(isfilemode==false){ + btnStop.disabled = true; btnStart.disabled = true; btnConnect.disabled=false; + setTimeout(function(){ + console.log("call stop ws!"); + wsconnecter.wsStop(); + isconnected=0; info_div.innerHTML="请点击连接";}, 3000 ); + rec.stop(function(blob,duration){ console.log(blob); @@ -189,8 +330,9 @@ function stop() { },function(errMsg){ console.log("errMsg: " + errMsg); }); + } // 停止连接 - + } diff --git a/funasr/runtime/html5/static/wsconnecter.js b/funasr/runtime/html5/static/wsconnecter.js index 676a94ae5..b9098bb5a 100644 --- a/funasr/runtime/html5/static/wsconnecter.js +++ b/funasr/runtime/html5/static/wsconnecter.js @@ -15,8 +15,7 @@ function WebSocketConnectMethod( config ) { //定义socket连接方法类 this.wsStart = function () { var Uri = document.getElementById('wssip').value; //"wss://111.205.137.58:5821/wss/" //设置wss asr online接口地址 如 wss://X.X.X.X:port/wss/ - - if(Uri.match(/wss:\S*/)) + if(Uri.match(/wss:\S*|ws:\S*/)) { console.log("Uri"+Uri); } @@ -25,6 +24,7 @@ function WebSocketConnectMethod( config ) { //定义socket连接方法类 alert("请检查wss地址正确性"); return 0; } + if ( 'WebSocket' in window ) { speechSokt = new WebSocket( Uri ); // 定义socket连接对象 speechSokt.onopen = function(e){onOpen(e);}; // 定义响应函数 @@ -80,6 +80,7 @@ function WebSocketConnectMethod( config ) { //定义socket连接方法类 speechSokt.send( JSON.stringify(request) ); console.log("连接成功"); stateHandle(0); + isconnected=1; } function onClose( e ) { @@ -92,9 +93,11 @@ function WebSocketConnectMethod( config ) { //定义socket连接方法类 } function onError( e ) { + isconnected=-1; info_div.innerHTML="连接"+e; console.log(e); stateHandle(2); + } From 113f8ea30a4a989a31ef993598fca0ff9158668b Mon Sep 17 00:00:00 2001 From: yhliang <68215459+yhliang-aslp@users.noreply.github.com> Date: Tue, 20 Jun 2023 13:04:19 +0800 Subject: [PATCH 3/4] Dev lyh (#657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update dev_lyh (#655) * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * update repo * Java ws client support (#651) * add java websocket support * make little changes * update repo * update repo * paraformer long cpu * typeguard==2.13.3 * Update readme.md * Update readme.md * export --------- Co-authored-by: 嘉渊 Co-authored-by: zhaomingwork <61895407+zhaomingwork@users.noreply.github.com> Co-authored-by: 游雁 Co-authored-by: Yabin Li * fix mfcca * update dev_lyh (#656) * export * update funasr-wss-client funasr-wss-server --------- Co-authored-by: 游雁 Co-authored-by: 雾聪 * Update default.py * Update default.py --------- Co-authored-by: 嘉渊 Co-authored-by: zhaomingwork <61895407+zhaomingwork@users.noreply.github.com> Co-authored-by: 游雁 Co-authored-by: Yabin Li --- .../sa_asr/conf/train_sa_asr_conformer.yaml | 1 + funasr/models/frontend/default.py | 101 +++++++++++------- 2 files changed, 64 insertions(+), 38 deletions(-) diff --git a/egs/alimeeting/sa_asr/conf/train_sa_asr_conformer.yaml b/egs/alimeeting/sa_asr/conf/train_sa_asr_conformer.yaml index 47bc6bdb6..18614ddf3 100644 --- a/egs/alimeeting/sa_asr/conf/train_sa_asr_conformer.yaml +++ b/egs/alimeeting/sa_asr/conf/train_sa_asr_conformer.yaml @@ -10,6 +10,7 @@ frontend_conf: lfr_m: 1 lfr_n: 1 use_channel: 0 + mc: False # encoder related asr_encoder: conformer diff --git a/funasr/models/frontend/default.py b/funasr/models/frontend/default.py index 6718f3f6c..abbcd1b05 100644 --- a/funasr/models/frontend/default.py +++ b/funasr/models/frontend/default.py @@ -77,8 +77,8 @@ class DefaultFrontend(AbsFrontend): htk=htk, ) self.n_mels = n_mels - self.frontend_type = "default" self.use_channel = use_channel + self.frontend_type = "default" def output_size(self) -> int: return self.n_mels @@ -146,9 +146,11 @@ class MultiChannelFrontend(AbsFrontend): def __init__( self, fs: Union[int, str] = 16000, - n_fft: int = 400, - frame_length: int = 25, - frame_shift: int = 10, + n_fft: int = 512, + win_length: int = None, + hop_length: int = None, + frame_length: int = None, + frame_shift: int = None, window: Optional[str] = "hann", center: bool = True, normalized: bool = False, @@ -162,7 +164,8 @@ class MultiChannelFrontend(AbsFrontend): use_channel: int = None, lfr_m: int = 1, lfr_n: int = 1, - cmvn_file: str = None + cmvn_file: str = None, + mc: bool = True ): assert check_argument_types() super().__init__() @@ -171,8 +174,18 @@ class MultiChannelFrontend(AbsFrontend): # Deepcopy (In general, dict shouldn't be used as default arg) frontend_conf = copy.deepcopy(frontend_conf) - self.win_length = frame_length * 16 - self.hop_length = frame_shift * 16 + if win_length is None and hop_length is None: + self.win_length = frame_length * 16 + self.hop_length = frame_shift * 16 + elif frame_length is None and frame_shift is None: + self.win_length = self.win_length + self.hop_length = self.hop_length + else: + logging.error( + "Only one of (win_length, hop_length) and (frame_length, frame_shift)" + "can be set." + ) + exit(1) if apply_stft: self.stft = Stft( @@ -202,17 +215,19 @@ class MultiChannelFrontend(AbsFrontend): htk=htk, ) self.n_mels = n_mels - self.frontend_type = "default" self.use_channel = use_channel - if self.use_channel is not None: - logging.info("use the channel %d" % (self.use_channel)) - else: - logging.info("random select channel") - self.cmvn_file = cmvn_file - if self.cmvn_file is not None: - mean, std = self._load_cmvn(self.cmvn_file) - self.register_buffer("mean", torch.from_numpy(mean)) - self.register_buffer("std", torch.from_numpy(std)) + self.mc = mc + if not self.mc: + if self.use_channel is not None: + logging.info("use the channel %d" % (self.use_channel)) + else: + logging.info("random select channel") + self.cmvn_file = cmvn_file + if self.cmvn_file is not None: + mean, std = self._load_cmvn(self.cmvn_file) + self.register_buffer("mean", torch.from_numpy(mean)) + self.register_buffer("std", torch.from_numpy(std)) + self.frontend_type = "multichannelfrontend" def output_size(self) -> int: return self.n_mels @@ -233,8 +248,8 @@ class MultiChannelFrontend(AbsFrontend): # input_stft: (Batch, Length, [Channel], Freq) input_stft, _, mask = self.frontend(input_stft, feats_lens) - # 3. [Multi channel case]: Select a channel - if input_stft.dim() == 4: + # 3. [Multi channel case]: Select a channel(sa_asr) + if input_stft.dim() == 4 and not self.mc: # h: (B, T, C, F) -> h: (B, T, F) if self.training: if self.use_channel is not None: @@ -256,27 +271,37 @@ class MultiChannelFrontend(AbsFrontend): # input_power: (Batch, [Channel,] Length, Freq) # -> input_feats: (Batch, Length, Dim) input_feats, _ = self.logmel(input_power, feats_lens) - - # 6. Apply CMVN - if self.cmvn_file is not None: - if feats_lens is None: - feats_lens = input_feats.new_full([input_feats.size(0)], input_feats.size(1)) - self.mean = self.mean.to(input_feats.device, input_feats.dtype) - self.std = self.std.to(input_feats.device, input_feats.dtype) - mask = make_pad_mask(feats_lens, input_feats, 1) - - if input_feats.requires_grad: - input_feats = input_feats + self.mean + if self.mc: + # MFCCA + if input_feats.dim() ==4: + bt = input_feats.size(0) + channel_size = input_feats.size(2) + input_feats = input_feats.transpose(1,2).reshape(bt*channel_size,-1,80).contiguous() + feats_lens = feats_lens.repeat(1,channel_size).squeeze() else: - input_feats += self.mean - if input_feats.requires_grad: - input_feats = input_feats.masked_fill(mask, 0.0) - else: - input_feats.masked_fill_(mask, 0.0) + channel_size = 1 + return input_feats, feats_lens, channel_size + else: + # 6. Apply CMVN + if self.cmvn_file is not None: + if feats_lens is None: + feats_lens = input_feats.new_full([input_feats.size(0)], input_feats.size(1)) + self.mean = self.mean.to(input_feats.device, input_feats.dtype) + self.std = self.std.to(input_feats.device, input_feats.dtype) + mask = make_pad_mask(feats_lens, input_feats, 1) - input_feats *= self.std + if input_feats.requires_grad: + input_feats = input_feats + self.mean + else: + input_feats += self.mean + if input_feats.requires_grad: + input_feats = input_feats.masked_fill(mask, 0.0) + else: + input_feats.masked_fill_(mask, 0.0) - return input_feats, feats_lens + input_feats *= self.std + + return input_feats, feats_lens def _compute_stft( self, input: torch.Tensor, input_lengths: torch.Tensor @@ -313,4 +338,4 @@ class MultiChannelFrontend(AbsFrontend): continue means = np.array(means_list).astype(np.float) vars = np.array(vars_list).astype(np.float) - return means, vars \ No newline at end of file + return means, vars From c2a2575f198b1bfd452ea5769bec81bcce3d3a42 Mon Sep 17 00:00:00 2001 From: manyeyes <32889020+manyeyes@users.noreply.github.com> Date: Wed, 21 Jun 2023 09:31:59 +0800 Subject: [PATCH 4/4] add c# assembly for fsmn vad (#650) Co-authored-by: zx <12345678> --- .../AliFsmnVadSharp.Examples.csproj | 18 + .../AliFsmnVadSharp.Examples/Program.cs | 61 ++ funasr/runtime/csharp/AliFsmnVadSharp.sln | 37 + .../csharp/AliFsmnVadSharp/AliFsmnVad.cs | 387 ++++++++++ .../AliFsmnVadSharp/AliFsmnVadSharp.csproj | 37 + .../AliFsmnVadSharp/DLL/KaldiNativeFbank.cs | 40 + .../AliFsmnVadSharp/DLL/KnfOnlineFbank.cs | 26 + .../csharp/AliFsmnVadSharp/E2EVadModel.cs | 717 ++++++++++++++++++ .../Lib/kaldi-native-fbank-dll.dll | Bin 0 -> 487936 bytes .../AliFsmnVadSharp/Model/CmvnEntity.cs | 17 + .../Model/E2EVadFrameProbEntity.cs | 23 + .../Model/E2EVadSpeechBufWithDoaEntity.cs | 98 +++ .../Model/EncoderConfEntity.cs | 35 + .../Model/FrontendConfEntity.cs | 29 + .../AliFsmnVadSharp/Model/SegmentEntity.cs | 22 + .../AliFsmnVadSharp/Model/VadInputEntity.cs | 23 + .../AliFsmnVadSharp/Model/VadOutputEntity.cs | 19 + .../Model/VadPostConfEntity.cs | 72 ++ .../AliFsmnVadSharp/Model/VadYamlEntity.cs | 27 + .../AliFsmnVadSharp/Struct/FbankData.cs | 6 + .../AliFsmnVadSharp/Utils/YamlHelper.cs | 28 + .../csharp/AliFsmnVadSharp/WavFrontend.cs | 185 +++++ .../csharp/AliFsmnVadSharp/WindowDetector.cs | 156 ++++ funasr/runtime/csharp/README.md | 59 ++ 24 files changed, 2122 insertions(+) create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp.Examples/AliFsmnVadSharp.Examples.csproj create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp.Examples/Program.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp.sln create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/AliFsmnVad.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/AliFsmnVadSharp.csproj create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/DLL/KaldiNativeFbank.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/DLL/KnfOnlineFbank.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/E2EVadModel.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Lib/kaldi-native-fbank-dll.dll create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Model/CmvnEntity.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Model/E2EVadFrameProbEntity.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Model/E2EVadSpeechBufWithDoaEntity.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Model/EncoderConfEntity.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Model/FrontendConfEntity.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Model/SegmentEntity.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Model/VadInputEntity.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Model/VadOutputEntity.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Model/VadPostConfEntity.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Model/VadYamlEntity.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Struct/FbankData.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/Utils/YamlHelper.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/WavFrontend.cs create mode 100644 funasr/runtime/csharp/AliFsmnVadSharp/WindowDetector.cs create mode 100644 funasr/runtime/csharp/README.md diff --git a/funasr/runtime/csharp/AliFsmnVadSharp.Examples/AliFsmnVadSharp.Examples.csproj b/funasr/runtime/csharp/AliFsmnVadSharp.Examples/AliFsmnVadSharp.Examples.csproj new file mode 100644 index 000000000..b494bb50c --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp.Examples/AliFsmnVadSharp.Examples.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + diff --git a/funasr/runtime/csharp/AliFsmnVadSharp.Examples/Program.cs b/funasr/runtime/csharp/AliFsmnVadSharp.Examples/Program.cs new file mode 100644 index 000000000..dd3bf78b9 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp.Examples/Program.cs @@ -0,0 +1,61 @@ +using AliFsmnVadSharp; +using AliFsmnVadSharp.Model; +using NAudio.Wave; + +internal static class Program +{ + [STAThread] + private static void Main() + { + string applicationBase = AppDomain.CurrentDomain.BaseDirectory; + string modelFilePath = applicationBase + "./speech_fsmn_vad_zh-cn-16k-common-pytorch/model.onnx"; + string configFilePath = applicationBase + "./speech_fsmn_vad_zh-cn-16k-common-pytorch/vad.yaml"; + string mvnFilePath = applicationBase + "./speech_fsmn_vad_zh-cn-16k-common-pytorch/vad.mvn"; + int batchSize = 2; + TimeSpan start_time0 = new TimeSpan(DateTime.Now.Ticks); + AliFsmnVad aliFsmnVad = new AliFsmnVad(modelFilePath, configFilePath, mvnFilePath, batchSize); + TimeSpan end_time0 = new TimeSpan(DateTime.Now.Ticks); + double elapsed_milliseconds0 = end_time0.TotalMilliseconds - start_time0.TotalMilliseconds; + Console.WriteLine("load model and init config elapsed_milliseconds:{0}", elapsed_milliseconds0.ToString()); + List samples = new List(); + TimeSpan total_duration = new TimeSpan(0L); + for (int i = 0; i < 2; i++) + { + string wavFilePath = string.Format(applicationBase + "./speech_fsmn_vad_zh-cn-16k-common-pytorch/example/{0}.wav", i.ToString());//vad_example + if (!File.Exists(wavFilePath)) + { + continue; + } + AudioFileReader _audioFileReader = new AudioFileReader(wavFilePath); + byte[] datas = new byte[_audioFileReader.Length]; + _audioFileReader.Read(datas, 0, datas.Length); + TimeSpan duration = _audioFileReader.TotalTime; + float[] wavdata = new float[datas.Length / 4]; + Buffer.BlockCopy(datas, 0, wavdata, 0, datas.Length); + float[] sample = wavdata.Select((float x) => x * 32768f).ToArray(); + samples.Add(wavdata); + total_duration += duration; + } + TimeSpan start_time = new TimeSpan(DateTime.Now.Ticks); + //SegmentEntity[] segments_duration = aliFsmnVad.GetSegments(samples); + SegmentEntity[] segments_duration = aliFsmnVad.GetSegmentsByStep(samples); + TimeSpan end_time = new TimeSpan(DateTime.Now.Ticks); + Console.WriteLine("vad infer result:"); + foreach (SegmentEntity segment in segments_duration) + { + Console.Write("["); + foreach (var x in segment.Segment) + { + Console.Write("[" + string.Join(",", x.ToArray()) + "]"); + } + Console.Write("]\r\n"); + } + + double elapsed_milliseconds = end_time.TotalMilliseconds - start_time.TotalMilliseconds; + double rtf = elapsed_milliseconds / total_duration.TotalMilliseconds; + Console.WriteLine("elapsed_milliseconds:{0}", elapsed_milliseconds.ToString()); + Console.WriteLine("total_duration:{0}", total_duration.TotalMilliseconds.ToString()); + Console.WriteLine("rtf:{1}", "0".ToString(), rtf.ToString()); + Console.WriteLine("------------------------"); + } +} \ No newline at end of file diff --git a/funasr/runtime/csharp/AliFsmnVadSharp.sln b/funasr/runtime/csharp/AliFsmnVadSharp.sln new file mode 100644 index 000000000..8bf24aa81 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliFsmnVadSharp", "AliFsmnVadSharp\AliFsmnVadSharp.csproj", "{BFB82F2E-AD5B-405C-AAFF-3CE33C548748}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliFsmnVadSharp.Examples", "AliFsmnVadSharp.Examples\AliFsmnVadSharp.Examples.csproj", "{2FFA4D03-A62B-435B-B57B-7E49209810E1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{212561CC-9836-4F45-A31B-298EF576F519}" + ProjectSection(SolutionItems) = preProject + license = license + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BFB82F2E-AD5B-405C-AAFF-3CE33C548748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFB82F2E-AD5B-405C-AAFF-3CE33C548748}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFB82F2E-AD5B-405C-AAFF-3CE33C548748}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFB82F2E-AD5B-405C-AAFF-3CE33C548748}.Release|Any CPU.Build.0 = Release|Any CPU + {2FFA4D03-A62B-435B-B57B-7E49209810E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FFA4D03-A62B-435B-B57B-7E49209810E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FFA4D03-A62B-435B-B57B-7E49209810E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FFA4D03-A62B-435B-B57B-7E49209810E1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FCC1BBCC-91A3-4223-B368-D272FB5108B6} + EndGlobalSection +EndGlobal diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/AliFsmnVad.cs b/funasr/runtime/csharp/AliFsmnVadSharp/AliFsmnVad.cs new file mode 100644 index 000000000..f42bfb12e --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/AliFsmnVad.cs @@ -0,0 +1,387 @@ +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.ML; +using Microsoft.ML.OnnxRuntime; +using Microsoft.ML.OnnxRuntime.Tensors; +using Microsoft.Extensions.Logging; +using AliFsmnVadSharp.Model; +using AliFsmnVadSharp.Utils; + +namespace AliFsmnVadSharp +{ + public class AliFsmnVad + { + private InferenceSession _onnxSession; + private readonly ILogger _logger; + private string _frontend; + private WavFrontend _wavFrontend; + private int _batchSize = 1; + private int _max_end_sil = int.MinValue; + private EncoderConfEntity _encoderConfEntity; + private VadPostConfEntity _vad_post_conf; + + public AliFsmnVad(string modelFilePath, string configFilePath, string mvnFilePath, int batchSize = 1) + { + Microsoft.ML.OnnxRuntime.SessionOptions options = new Microsoft.ML.OnnxRuntime.SessionOptions(); + options.AppendExecutionProvider_CPU(0); + options.InterOpNumThreads = 1; + _onnxSession = new InferenceSession(modelFilePath, options); + + VadYamlEntity vadYamlEntity = YamlHelper.ReadYaml(configFilePath); + _wavFrontend = new WavFrontend(mvnFilePath, vadYamlEntity.frontend_conf); + _frontend = vadYamlEntity.frontend; + _vad_post_conf = vadYamlEntity.vad_post_conf; + _batchSize = batchSize; + _max_end_sil = _max_end_sil != int.MinValue ? _max_end_sil : vadYamlEntity.vad_post_conf.max_end_silence_time; + _encoderConfEntity = vadYamlEntity.encoder_conf; + + ILoggerFactory loggerFactory = new LoggerFactory(); + _logger = new Logger(loggerFactory); + } + + public SegmentEntity[] GetSegments(List samples) + { + int waveform_nums = samples.Count; + _batchSize = Math.Min(waveform_nums, _batchSize); + SegmentEntity[] segments = new SegmentEntity[waveform_nums]; + for (int beg_idx = 0; beg_idx < waveform_nums; beg_idx += _batchSize) + { + int end_idx = Math.Min(waveform_nums, beg_idx + _batchSize); + List waveform_list = new List(); + for (int i = beg_idx; i < end_idx; i++) + { + waveform_list.Add(samples[i]); + } + List vadInputEntitys = ExtractFeats(waveform_list); + try + { + int t_offset = 0; + int step = Math.Min(waveform_list.Max(x => x.Length), 6000); + bool is_final = true; + List vadOutputEntitys = Infer(vadInputEntitys); + for (int batch_num = beg_idx; batch_num < end_idx; batch_num++) + { + var scores = vadOutputEntitys[batch_num - beg_idx].Scores; + SegmentEntity[] segments_part = vadInputEntitys[batch_num].VadScorer.DefaultCall(scores, waveform_list[batch_num - beg_idx], is_final: is_final, max_end_sil: _max_end_sil, online: false); + if (segments_part.Length > 0) + { +#pragma warning disable CS8602 // 解引用可能出现空引用。 + if (segments[batch_num] == null) + { + segments[batch_num] = new SegmentEntity(); + } + segments[batch_num].Segment.AddRange(segments_part[0].Segment); // +#pragma warning restore CS8602 // 解引用可能出现空引用。 + + } + } + } + catch (OnnxRuntimeException ex) + { + _logger.LogWarning("input wav is silence or noise"); + segments = null; + } +// for (int batch_num = 0; batch_num < _batchSize; batch_num++) +// { +// List segment_waveforms = new List(); +// foreach (int[] segment in segments[beg_idx + batch_num].Segment) +// { +// // (int)(16000 * (segment[0] / 1000.0) * 2); +// int frame_length = (((6000 * 400) / 400 - 1) * 160 + 400) / 60 / 1000; +// int frame_start = segment[0] * frame_length; +// int frame_end = segment[1] * frame_length; +// float[] segment_waveform = new float[frame_end - frame_start]; +// Array.Copy(waveform_list[batch_num], frame_start, segment_waveform, 0, segment_waveform.Length); +// segment_waveforms.Add(segment_waveform); +// } +// segments[beg_idx + batch_num].Waveform.AddRange(segment_waveforms); +// } + } + + return segments; + } + + public SegmentEntity[] GetSegmentsByStep(List samples) + { + int waveform_nums = samples.Count; + _batchSize=Math.Min(waveform_nums, _batchSize); + SegmentEntity[] segments = new SegmentEntity[waveform_nums]; + for (int beg_idx = 0; beg_idx < waveform_nums; beg_idx += _batchSize) + { + int end_idx = Math.Min(waveform_nums, beg_idx + _batchSize); + List waveform_list = new List(); + for (int i = beg_idx; i < end_idx; i++) + { + waveform_list.Add(samples[i]); + } + List vadInputEntitys = ExtractFeats(waveform_list); + int feats_len = vadInputEntitys.Max(x => x.SpeechLength); + List in_cache = new List(); + in_cache = PrepareCache(in_cache); + try + { + int step = Math.Min(vadInputEntitys.Max(x => x.SpeechLength), 6000 * 400); + bool is_final = true; + for (int t_offset = 0; t_offset < (int)(feats_len); t_offset += Math.Min(step, feats_len - t_offset)) + { + + if (t_offset + step >= feats_len - 1) + { + step = feats_len - t_offset; + is_final = true; + } + else + { + is_final = false; + } + List vadInputEntitys_step = new List(); + foreach (VadInputEntity vadInputEntity in vadInputEntitys) + { + VadInputEntity vadInputEntity_step = new VadInputEntity(); + float[]? feats = vadInputEntity.Speech; + int curr_step = Math.Min(feats.Length - t_offset, step); + if (curr_step <= 0) + { + vadInputEntity_step.Speech = new float[32000]; + vadInputEntity_step.SpeechLength = 0; + vadInputEntity_step.InCaches = in_cache; + vadInputEntity_step.Waveform = new float[(((int)(32000) / 400 - 1) * 160 + 400)]; + vadInputEntitys_step.Add(vadInputEntity_step); + continue; + } + float[]? feats_step = new float[curr_step]; + Array.Copy(feats, t_offset, feats_step, 0, feats_step.Length); + float[]? waveform = vadInputEntity.Waveform; + float[]? waveform_step = new float[Math.Min(waveform.Length, ((int)(t_offset + step) / 400 - 1) * 160 + 400) - t_offset / 400 * 160]; + Array.Copy(waveform, t_offset / 400 * 160, waveform_step, 0, waveform_step.Length); + vadInputEntity_step.Speech = feats_step; + vadInputEntity_step.SpeechLength = feats_step.Length; + vadInputEntity_step.InCaches = vadInputEntity.InCaches; + vadInputEntity_step.Waveform = waveform_step; + vadInputEntitys_step.Add(vadInputEntity_step); + } + List vadOutputEntitys = Infer(vadInputEntitys_step); + for (int batch_num = 0; batch_num < _batchSize; batch_num++) + { + vadInputEntitys[batch_num].InCaches = vadOutputEntitys[batch_num].OutCaches; + var scores = vadOutputEntitys[batch_num].Scores; + SegmentEntity[] segments_part = vadInputEntitys[batch_num].VadScorer.DefaultCall(scores, vadInputEntitys_step[batch_num].Waveform, is_final: is_final, max_end_sil: _max_end_sil, online: false); + if (segments_part.Length > 0) + { + +#pragma warning disable CS8602 // 解引用可能出现空引用。 + if (segments[beg_idx + batch_num] == null) + { + segments[beg_idx + batch_num] = new SegmentEntity(); + } + if (segments_part[0] != null) + { + segments[beg_idx + batch_num].Segment.AddRange(segments_part[0].Segment); + } +#pragma warning restore CS8602 // 解引用可能出现空引用。 + + } + } + } + } + catch (OnnxRuntimeException ex) + { + _logger.LogWarning("input wav is silence or noise"); + segments = null; + } +// for (int batch_num = 0; batch_num < _batchSize; batch_num++) +// { +// List segment_waveforms=new List(); +// foreach (int[] segment in segments[beg_idx + batch_num].Segment) +// { +// // (int)(16000 * (segment[0] / 1000.0) * 2); +// int frame_length = (((6000 * 400) / 400 - 1) * 160 + 400) / 60 / 1000; +// int frame_start = segment[0] * frame_length; +// int frame_end = segment[1] * frame_length; +// if(frame_end > waveform_list[batch_num].Length) +// { +// break; +// } +// float[] segment_waveform = new float[frame_end - frame_start]; +// Array.Copy(waveform_list[batch_num], frame_start, segment_waveform, 0, segment_waveform.Length); +// segment_waveforms.Add(segment_waveform); +// } +// segments[beg_idx + batch_num].Waveform.AddRange(segment_waveforms); +// } + + } + return segments; + } + + private List PrepareCache(List in_cache) + { + if (in_cache.Count > 0) + { + return in_cache; + } + + int fsmn_layers = _encoderConfEntity.fsmn_layers; + + int proj_dim = _encoderConfEntity.proj_dim; + int lorder = _encoderConfEntity.lorder; + + for (int i = 0; i < fsmn_layers; i++) + { + float[] cache = new float[1 * proj_dim * (lorder - 1) * 1]; + in_cache.Add(cache); + } + return in_cache; + } + + private List ExtractFeats(List waveform_list) + { + List in_cache = new List(); + in_cache = PrepareCache(in_cache); + List vadInputEntitys = new List(); + foreach (var waveform in waveform_list) + { + float[] fbanks = _wavFrontend.GetFbank(waveform); + float[] features = _wavFrontend.LfrCmvn(fbanks); + VadInputEntity vadInputEntity = new VadInputEntity(); + vadInputEntity.Waveform = waveform; + vadInputEntity.Speech = features; + vadInputEntity.SpeechLength = features.Length; + vadInputEntity.InCaches = in_cache; + vadInputEntity.VadScorer = new E2EVadModel(_vad_post_conf); + vadInputEntitys.Add(vadInputEntity); + } + return vadInputEntitys; + } + /// + /// 一维数组转3维数组 + /// + /// + /// 一维长 + /// 二维长 + /// + public static T[,,] DimOneToThree(T[] oneDimObj, int len, int wid) + { + if (oneDimObj.Length % (len * wid) != 0) + return null; + int height = oneDimObj.Length / (len * wid); + T[,,] threeDimObj = new T[len, wid, height]; + + for (int i = 0; i < oneDimObj.Length; i++) + { + threeDimObj[i / (wid * height), (i / height) % wid, i % height] = oneDimObj[i]; + } + return threeDimObj; + } + + private List Infer(List vadInputEntitys) + { + List vadOutputEntities = new List(); + foreach (VadInputEntity vadInputEntity in vadInputEntitys) + { + int batchSize = 1;//_batchSize + var inputMeta = _onnxSession.InputMetadata; + var container = new List(); + int[] dim = new int[] { batchSize, vadInputEntity.Speech.Length / 400 / batchSize, 400 }; + var tensor = new DenseTensor(vadInputEntity.Speech, dim, false); + container.Add(NamedOnnxValue.CreateFromTensor("speech", tensor)); + + int i = 0; + foreach (var cache in vadInputEntity.InCaches) + { + int[] cache_dim = new int[] { 1, 128, cache.Length / 128 / 1, 1 }; + var cache_tensor = new DenseTensor(cache, cache_dim, false); + container.Add(NamedOnnxValue.CreateFromTensor("in_cache" + i.ToString(), cache_tensor)); + i++; + } + + IDisposableReadOnlyCollection results = _onnxSession.Run(container); + var resultsArray = results.ToArray(); + VadOutputEntity vadOutputEntity = new VadOutputEntity(); + for (int j = 0; j < resultsArray.Length; j++) + { + if (resultsArray[j].Name.Equals("logits")) + { + Tensor tensors = resultsArray[0].AsTensor(); + var _scores = DimOneToThree(tensors.ToArray(), 1, tensors.Dimensions[1]); + vadOutputEntity.Scores = _scores; + } + if (resultsArray[j].Name.StartsWith("out_cache")) + { + vadOutputEntity.OutCaches.Add(resultsArray[j].AsEnumerable().ToArray()); + } + + } + vadOutputEntities.Add(vadOutputEntity); + } + + return vadOutputEntities; + } + + private float[] PadSequence(List modelInputs) + { + int max_speech_length = modelInputs.Max(x => x.SpeechLength); + int speech_length = max_speech_length * modelInputs.Count; + float[] speech = new float[speech_length]; + float[,] xxx = new float[modelInputs.Count, max_speech_length]; + for (int i = 0; i < modelInputs.Count; i++) + { + if (max_speech_length == modelInputs[i].SpeechLength) + { + for (int j = 0; j < xxx.GetLength(1); j++) + { +#pragma warning disable CS8602 // 解引用可能出现空引用。 + xxx[i, j] = modelInputs[i].Speech[j]; +#pragma warning restore CS8602 // 解引用可能出现空引用。 + } + continue; + } + float[] nullspeech = new float[max_speech_length - modelInputs[i].SpeechLength]; + float[]? curr_speech = modelInputs[i].Speech; + float[] padspeech = new float[max_speech_length]; + // /////////////////////////////////////////////////// + var arr_neg_mean = _onnxSession.ModelMetadata.CustomMetadataMap["neg_mean"].ToString().Split(',').ToArray(); + double[] neg_mean = arr_neg_mean.Select(x => (double)Convert.ToDouble(x)).ToArray(); + var arr_inv_stddev = _onnxSession.ModelMetadata.CustomMetadataMap["inv_stddev"].ToString().Split(',').ToArray(); + double[] inv_stddev = arr_inv_stddev.Select(x => (double)Convert.ToDouble(x)).ToArray(); + + int dim = neg_mean.Length; + for (int j = 0; j < max_speech_length; j++) + { + int k = new Random().Next(0, dim); + padspeech[j] = (float)((float)(0 + neg_mean[k]) * inv_stddev[k]); + } + Array.Copy(curr_speech, 0, padspeech, 0, curr_speech.Length); + for (int j = 0; j < padspeech.Length; j++) + { +#pragma warning disable CS8602 // 解引用可能出现空引用。 + xxx[i, j] = padspeech[j]; +#pragma warning restore CS8602 // 解引用可能出现空引用。 + } + + } + int s = 0; + for (int i = 0; i < xxx.GetLength(0); i++) + { + for (int j = 0; j < xxx.GetLength(1); j++) + { + speech[s] = xxx[i, j]; + s++; + } + } + return speech; + } + + + + + + + + + + + + + } +} \ No newline at end of file diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/AliFsmnVadSharp.csproj b/funasr/runtime/csharp/AliFsmnVadSharp/AliFsmnVadSharp.csproj new file mode 100644 index 000000000..49915173e --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/AliFsmnVadSharp.csproj @@ -0,0 +1,37 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + PreserveNewest + kaldi-native-fbank-dll.dll + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/DLL/KaldiNativeFbank.cs b/funasr/runtime/csharp/AliFsmnVadSharp/DLL/KaldiNativeFbank.cs new file mode 100644 index 000000000..af0ad3664 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/DLL/KaldiNativeFbank.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +using AliFsmnVadSharp.Struct; + +namespace AliFsmnVadSharp.DLL +{ + public static class KaldiNativeFbank + { + private const string dllName = @"kaldi-native-fbank-dll"; + + [DllImport(dllName, EntryPoint = "GetFbankOptions", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr GetFbankOptions(float dither, bool snip_edges, float sample_rate, int num_bins, float frame_shift = 10.0f, float frame_length = 25.0f, float energy_floor = 0.0f, bool debug_mel = false, string window_type = "hamming"); + + [DllImport(dllName, EntryPoint = "GetOnlineFbank", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + internal static extern KnfOnlineFbank GetOnlineFbank(IntPtr opts); + + [DllImport(dllName, EntryPoint = "AcceptWaveform", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + internal static extern void AcceptWaveform(KnfOnlineFbank knfOnlineFbank, float sample_rate, float[] samples, int samples_size); + + [DllImport(dllName, EntryPoint = "InputFinished", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + internal static extern void InputFinished(KnfOnlineFbank knfOnlineFbank); + + [DllImport(dllName, EntryPoint = "GetNumFramesReady", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + internal static extern int GetNumFramesReady(KnfOnlineFbank knfOnlineFbank); + + [DllImport(dllName, EntryPoint = "AcceptWaveformxxx", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + internal static extern FbankDatas AcceptWaveformxxx(KnfOnlineFbank knfOnlineFbank, float sample_rate, float[] samples, int samples_size); + + [DllImport(dllName, EntryPoint = "GetFbank", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + internal static extern void GetFbank(KnfOnlineFbank knfOnlineFbank,int frame, ref FbankData pData); + + [DllImport(dllName, EntryPoint = "GetFbanks", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + internal static extern void GetFbanks(KnfOnlineFbank knfOnlineFbank, int framesNum, ref FbankDatas fbankDatas); + + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/DLL/KnfOnlineFbank.cs b/funasr/runtime/csharp/AliFsmnVadSharp/DLL/KnfOnlineFbank.cs new file mode 100644 index 000000000..45549b2bf --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/DLL/KnfOnlineFbank.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp.DLL +{ + internal struct FbankData + { + public IntPtr data; + public int data_length; + }; + + internal struct FbankDatas + { + public IntPtr data; + public int data_length; + }; + + internal struct KnfOnlineFbank + { + public IntPtr impl; + }; +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/E2EVadModel.cs b/funasr/runtime/csharp/AliFsmnVadSharp/E2EVadModel.cs new file mode 100644 index 000000000..ce519b1e9 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/E2EVadModel.cs @@ -0,0 +1,717 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AliFsmnVadSharp.Model; + +namespace AliFsmnVadSharp +{ + enum VadStateMachine + { + kVadInStateStartPointNotDetected = 1, + kVadInStateInSpeechSegment = 2, + kVadInStateEndPointDetected = 3, + } + enum VadDetectMode + { + kVadSingleUtteranceDetectMode = 0, + kVadMutipleUtteranceDetectMode = 1, + } + + + internal class E2EVadModel + { + private VadPostConfEntity _vad_opts = new VadPostConfEntity(); + private WindowDetector _windows_detector = new WindowDetector(); + private bool _is_final = false; + private int _data_buf_start_frame = 0; + private int _frm_cnt = 0; + private int _latest_confirmed_speech_frame = 0; + private int _lastest_confirmed_silence_frame = -1; + private int _continous_silence_frame_count = 0; + private int _vad_state_machine = (int)VadStateMachine.kVadInStateStartPointNotDetected; + private int _confirmed_start_frame = -1; + private int _confirmed_end_frame = -1; + private int _number_end_time_detected = 0; + private int _sil_frame = 0; + private int[] _sil_pdf_ids = new int[0]; + private double _noise_average_decibel = -100.0D; + private bool _pre_end_silence_detected = false; + private bool _next_seg = true; + + private List _output_data_buf; + private int _output_data_buf_offset = 0; + private List _frame_probs = new List(); + private int _max_end_sil_frame_cnt_thresh = 800 - 150; + private float _speech_noise_thres = 0.6F; + private float[,,] _scores = null; + private int _idx_pre_chunk = 0; + private bool _max_time_out = false; + private List _decibel = new List(); + private int _data_buf_size = 0; + private int _data_buf_all_size = 0; + + public E2EVadModel(VadPostConfEntity vadPostConfEntity) + { + _vad_opts = vadPostConfEntity; + _windows_detector = new WindowDetector(_vad_opts.window_size_ms, + _vad_opts.sil_to_speech_time_thres, + _vad_opts.speech_to_sil_time_thres, + _vad_opts.frame_in_ms); + AllResetDetection(); + } + + private void AllResetDetection() + { + _is_final = false; + _data_buf_start_frame = 0; + _frm_cnt = 0; + _latest_confirmed_speech_frame = 0; + _lastest_confirmed_silence_frame = -1; + _continous_silence_frame_count = 0; + _vad_state_machine = (int)VadStateMachine.kVadInStateStartPointNotDetected; + _confirmed_start_frame = -1; + _confirmed_end_frame = -1; + _number_end_time_detected = 0; + _sil_frame = 0; + _sil_pdf_ids = _vad_opts.sil_pdf_ids; + _noise_average_decibel = -100.0F; + _pre_end_silence_detected = false; + _next_seg = true; + + _output_data_buf = new List(); + _output_data_buf_offset = 0; + _frame_probs = new List(); + _max_end_sil_frame_cnt_thresh = _vad_opts.max_end_silence_time - _vad_opts.speech_to_sil_time_thres; + _speech_noise_thres = _vad_opts.speech_noise_thres; + _scores = null; + _idx_pre_chunk = 0; + _max_time_out = false; + _decibel = new List(); + _data_buf_size = 0; + _data_buf_all_size = 0; + ResetDetection(); + } + + private void ResetDetection() + { + _continous_silence_frame_count = 0; + _latest_confirmed_speech_frame = 0; + _lastest_confirmed_silence_frame = -1; + _confirmed_start_frame = -1; + _confirmed_end_frame = -1; + _vad_state_machine = (int)VadStateMachine.kVadInStateStartPointNotDetected; + _windows_detector.Reset(); + _sil_frame = 0; + _frame_probs = new List(); + } + + private void ComputeDecibel(float[] waveform) + { + int frame_sample_length = (int)(_vad_opts.frame_length_ms * _vad_opts.sample_rate / 1000); + int frame_shift_length = (int)(_vad_opts.frame_in_ms * _vad_opts.sample_rate / 1000); + if (_data_buf_all_size == 0) + { + _data_buf_all_size = waveform.Length; + _data_buf_size = _data_buf_all_size; + } + else + { + _data_buf_all_size += waveform.Length; + } + + for (int offset = 0; offset < waveform.Length - frame_sample_length + 1; offset += frame_shift_length) + { + float[] _waveform_chunk = new float[frame_sample_length]; + Array.Copy(waveform, offset, _waveform_chunk, 0, _waveform_chunk.Length); + float[] _waveform_chunk_pow = _waveform_chunk.Select(x => (float)Math.Pow((double)x, 2)).ToArray(); + _decibel.Add( + 10 * Math.Log10( + _waveform_chunk_pow.Sum() + 0.000001 + ) + ); + } + + } + + private void ComputeScores(float[,,] scores) + { + _vad_opts.nn_eval_block_size = scores.GetLength(1); + _frm_cnt += scores.GetLength(1); + _scores = scores; + } + + private void PopDataBufTillFrame(int frame_idx)// need check again + { + while (_data_buf_start_frame < frame_idx) + { + if (_data_buf_size >= (int)(_vad_opts.frame_in_ms * _vad_opts.sample_rate / 1000)) + { + _data_buf_start_frame += 1; + _data_buf_size = _data_buf_all_size - _data_buf_start_frame * (int)(_vad_opts.frame_in_ms * _vad_opts.sample_rate / 1000); + } + } + } + + private void PopDataToOutputBuf(int start_frm, int frm_cnt, bool first_frm_is_start_point, + bool last_frm_is_end_point, bool end_point_is_sent_end) + { + PopDataBufTillFrame(start_frm); + int expected_sample_number = (int)(frm_cnt * _vad_opts.sample_rate * _vad_opts.frame_in_ms / 1000); + if (last_frm_is_end_point) + { + int extra_sample = Math.Max(0, (int)(_vad_opts.frame_length_ms * _vad_opts.sample_rate / 1000 - _vad_opts.sample_rate * _vad_opts.frame_in_ms / 1000)); + expected_sample_number += (int)(extra_sample); + } + + if (end_point_is_sent_end) + { + expected_sample_number = Math.Max(expected_sample_number, _data_buf_size); + } + if (_data_buf_size < expected_sample_number) + { + Console.WriteLine("error in calling pop data_buf\n"); + } + + if (_output_data_buf.Count == 0 || first_frm_is_start_point) + { + _output_data_buf.Add(new E2EVadSpeechBufWithDoaEntity()); + _output_data_buf.Last().Reset(); + _output_data_buf.Last().start_ms = start_frm * _vad_opts.frame_in_ms; + _output_data_buf.Last().end_ms = _output_data_buf.Last().start_ms; + _output_data_buf.Last().doa = 0; + } + + E2EVadSpeechBufWithDoaEntity cur_seg = _output_data_buf.Last(); + if (cur_seg.end_ms != start_frm * _vad_opts.frame_in_ms) + { + Console.WriteLine("warning\n"); + } + + int out_pos = cur_seg.buffer.Length; // cur_seg.buff现在没做任何操作 + int data_to_pop = 0; + if (end_point_is_sent_end) + { + data_to_pop = expected_sample_number; + } + else + { + data_to_pop = (int)(frm_cnt * _vad_opts.frame_in_ms * _vad_opts.sample_rate / 1000); + } + if (data_to_pop > _data_buf_size) + { + Console.WriteLine("VAD data_to_pop is bigger than _data_buf_size!!!\n"); + data_to_pop = _data_buf_size; + expected_sample_number = _data_buf_size; + } + + + cur_seg.doa = 0; + for (int sample_cpy_out = 0; sample_cpy_out < data_to_pop; sample_cpy_out++) + { + out_pos += 1; + } + for (int sample_cpy_out = data_to_pop; sample_cpy_out < expected_sample_number; sample_cpy_out++) + { + out_pos += 1; + } + + if (cur_seg.end_ms != start_frm * _vad_opts.frame_in_ms) + { + Console.WriteLine("Something wrong with the VAD algorithm\n"); + } + + _data_buf_start_frame += frm_cnt; + cur_seg.end_ms = (start_frm + frm_cnt) * _vad_opts.frame_in_ms; + if (first_frm_is_start_point) + { + cur_seg.contain_seg_start_point = true; + } + + if (last_frm_is_end_point) + { + cur_seg.contain_seg_end_point = true; + } + } + + private void OnSilenceDetected(int valid_frame) + { + _lastest_confirmed_silence_frame = valid_frame; + if (_vad_state_machine == (int)VadStateMachine.kVadInStateStartPointNotDetected) + { + PopDataBufTillFrame(valid_frame); + } + + } + + private void OnVoiceDetected(int valid_frame) + { + _latest_confirmed_speech_frame = valid_frame; + PopDataToOutputBuf(valid_frame, 1, false, false, false); + } + + private void OnVoiceStart(int start_frame, bool fake_result = false) + { + if (_vad_opts.do_start_point_detection) + { + //do nothing + } + if (_confirmed_start_frame != -1) + { + + Console.WriteLine("not reset vad properly\n"); + } + else + { + _confirmed_start_frame = start_frame; + } + if (!fake_result || _vad_state_machine == (int)VadStateMachine.kVadInStateStartPointNotDetected) + { + + PopDataToOutputBuf(_confirmed_start_frame, 1, true, false, false); + } + } + + private void OnVoiceEnd(int end_frame, bool fake_result, bool is_last_frame) + { + for (int t = _latest_confirmed_speech_frame + 1; t < end_frame; t++) + { + OnVoiceDetected(t); + } + if (_vad_opts.do_end_point_detection) + { + //do nothing + } + if (_confirmed_end_frame != -1) + { + Console.WriteLine("not reset vad properly\n"); + } + else + { + _confirmed_end_frame = end_frame; + } + if (!fake_result) + { + _sil_frame = 0; + PopDataToOutputBuf(_confirmed_end_frame, 1, false, true, is_last_frame); + } + _number_end_time_detected += 1; + } + + private void MaybeOnVoiceEndIfLastFrame(bool is_final_frame, int cur_frm_idx) + { + if (is_final_frame) + { + OnVoiceEnd(cur_frm_idx, false, true); + _vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected; + } + } + + private int GetLatency() + { + return (int)(LatencyFrmNumAtStartPoint() * _vad_opts.frame_in_ms); + } + + private int LatencyFrmNumAtStartPoint() + { + int vad_latency = _windows_detector.GetWinSize(); + if (_vad_opts.do_extend != 0) + { + vad_latency += (int)(_vad_opts.lookback_time_start_point / _vad_opts.frame_in_ms); + } + return vad_latency; + } + + private FrameState GetFrameState(int t) + { + + FrameState frame_state = FrameState.kFrameStateInvalid; + double cur_decibel = _decibel[t]; + double cur_snr = cur_decibel - _noise_average_decibel; + if (cur_decibel < _vad_opts.decibel_thres) + { + frame_state = FrameState.kFrameStateSil; + DetectOneFrame(frame_state, t, false); + return frame_state; + } + + + double sum_score = 0.0D; + double noise_prob = 0.0D; + Trace.Assert(_sil_pdf_ids.Length == _vad_opts.silence_pdf_num, ""); + if (_sil_pdf_ids.Length > 0) + { + Trace.Assert(_scores.GetLength(0) == 1, "只支持batch_size = 1的测试"); // 只支持batch_size = 1的测试 + float[] sil_pdf_scores = new float[_sil_pdf_ids.Length]; + int j = 0; + foreach (int sil_pdf_id in _sil_pdf_ids) + { + sil_pdf_scores[j] = _scores[0,t - _idx_pre_chunk,sil_pdf_id]; + j++; + } + sum_score = sil_pdf_scores.Length == 0 ? 0 : sil_pdf_scores.Sum(); + noise_prob = Math.Log(sum_score) * _vad_opts.speech_2_noise_ratio; + double total_score = 1.0D; + sum_score = total_score - sum_score; + } + double speech_prob = Math.Log(sum_score); + if (_vad_opts.output_frame_probs) + { + E2EVadFrameProbEntity frame_prob = new E2EVadFrameProbEntity(); + frame_prob.noise_prob = noise_prob; + frame_prob.speech_prob = speech_prob; + frame_prob.score = sum_score; + frame_prob.frame_id = t; + _frame_probs.Add(frame_prob); + } + + if (Math.Exp(speech_prob) >= Math.Exp(noise_prob) + _speech_noise_thres) + { + if (cur_snr >= _vad_opts.snr_thres && cur_decibel >= _vad_opts.decibel_thres) + { + frame_state = FrameState.kFrameStateSpeech; + } + else + { + frame_state = FrameState.kFrameStateSil; + } + } + else + { + frame_state = FrameState.kFrameStateSil; + if (_noise_average_decibel < -99.9) + { + _noise_average_decibel = cur_decibel; + } + else + { + _noise_average_decibel = (cur_decibel + _noise_average_decibel * (_vad_opts.noise_frame_num_used_for_snr - 1)) / _vad_opts.noise_frame_num_used_for_snr; + } + } + return frame_state; + } + + public SegmentEntity[] DefaultCall(float[,,] score, float[] waveform, + bool is_final = false, int max_end_sil = 800, bool online = false + ) + { + _max_end_sil_frame_cnt_thresh = max_end_sil - _vad_opts.speech_to_sil_time_thres; + // compute decibel for each frame + ComputeDecibel(waveform); + ComputeScores(score); + if (!is_final) + { + DetectCommonFrames(); + } + else + { + DetectLastFrames(); + } + int batchSize = score.GetLength(0); + SegmentEntity[] segments = new SegmentEntity[batchSize]; + for (int batch_num = 0; batch_num < batchSize; batch_num++) // only support batch_size = 1 now + { + List segment_batch = new List(); + if (_output_data_buf.Count > 0) + { + for (int i = _output_data_buf_offset; i < _output_data_buf.Count; i++) + { + int start_ms; + int end_ms; + if (online) + { + if (!_output_data_buf[i].contain_seg_start_point) + { + continue; + } + if (!_next_seg && !_output_data_buf[i].contain_seg_end_point) + { + continue; + } + start_ms = _next_seg ? _output_data_buf[i].start_ms : -1; + if (_output_data_buf[i].contain_seg_end_point) + { + end_ms = _output_data_buf[i].end_ms; + _next_seg = true; + _output_data_buf_offset += 1; + } + else + { + end_ms = -1; + _next_seg = false; + } + } + else + { + if (!is_final && (!_output_data_buf[i].contain_seg_start_point || !_output_data_buf[i].contain_seg_end_point)) + { + continue; + } + start_ms = _output_data_buf[i].start_ms; + end_ms = _output_data_buf[i].end_ms; + _output_data_buf_offset += 1; + + } + int[] segment_ms = new int[] { start_ms, end_ms }; + segment_batch.Add(segment_ms); + + } + + } + + if (segment_batch.Count > 0) + { + if (segments[batch_num] == null) + { + segments[batch_num] = new SegmentEntity(); + } + segments[batch_num].Segment.AddRange(segment_batch); + } + } + + if (is_final) + { + // reset class variables and clear the dict for the next query + AllResetDetection(); + } + + return segments; + } + + private int DetectCommonFrames() + { + if (_vad_state_machine == (int)VadStateMachine.kVadInStateEndPointDetected) + { + return 0; + } + for (int i = _vad_opts.nn_eval_block_size - 1; i > -1; i += -1) + { + FrameState frame_state = FrameState.kFrameStateInvalid; + frame_state = GetFrameState(_frm_cnt - 1 - i); + DetectOneFrame(frame_state, _frm_cnt - 1 - i, false); + } + + _idx_pre_chunk += _scores.GetLength(1)* _scores.GetLength(0); //_scores.shape[1]; + return 0; + } + + private int DetectLastFrames() + { + if (_vad_state_machine == (int)VadStateMachine.kVadInStateEndPointDetected) + { + return 0; + } + for (int i = _vad_opts.nn_eval_block_size - 1; i > -1; i += -1) + { + FrameState frame_state = FrameState.kFrameStateInvalid; + frame_state = GetFrameState(_frm_cnt - 1 - i); + if (i != 0) + { + DetectOneFrame(frame_state, _frm_cnt - 1 - i, false); + } + else + { + DetectOneFrame(frame_state, _frm_cnt - 1, true); + } + + + } + + return 0; + } + + private void DetectOneFrame(FrameState cur_frm_state, int cur_frm_idx, bool is_final_frame) + { + FrameState tmp_cur_frm_state = FrameState.kFrameStateInvalid; + if (cur_frm_state == FrameState.kFrameStateSpeech) + { + if (Math.Abs(1.0) > _vad_opts.fe_prior_thres)//Fabs + { + tmp_cur_frm_state = FrameState.kFrameStateSpeech; + } + else + { + tmp_cur_frm_state = FrameState.kFrameStateSil; + } + } + else if (cur_frm_state == FrameState.kFrameStateSil) + { + tmp_cur_frm_state = FrameState.kFrameStateSil; + } + + AudioChangeState state_change = _windows_detector.DetectOneFrame(tmp_cur_frm_state, cur_frm_idx); + int frm_shift_in_ms = _vad_opts.frame_in_ms; + if (AudioChangeState.kChangeStateSil2Speech == state_change) + { + int silence_frame_count = _continous_silence_frame_count; // no used + _continous_silence_frame_count = 0; + _pre_end_silence_detected = false; + int start_frame = 0; + if (_vad_state_machine == (int)VadStateMachine.kVadInStateStartPointNotDetected) + { + start_frame = Math.Max(_data_buf_start_frame, cur_frm_idx - LatencyFrmNumAtStartPoint()); + OnVoiceStart(start_frame); + _vad_state_machine = (int)VadStateMachine.kVadInStateInSpeechSegment; + for (int t = start_frame + 1; t < cur_frm_idx + 1; t++) + { + OnVoiceDetected(t); + } + + } + else if (_vad_state_machine == (int)VadStateMachine.kVadInStateInSpeechSegment) + { + for (int t = _latest_confirmed_speech_frame + 1; t < cur_frm_idx; t++) + { + OnVoiceDetected(t); + } + if (cur_frm_idx - _confirmed_start_frame + 1 > _vad_opts.max_single_segment_time / frm_shift_in_ms) + { + OnVoiceEnd(cur_frm_idx, false, false); + _vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected; + } + + else if (!is_final_frame) + { + OnVoiceDetected(cur_frm_idx); + } + else + { + MaybeOnVoiceEndIfLastFrame(is_final_frame, cur_frm_idx); + } + + } + else + { + return; + } + } + else if (AudioChangeState.kChangeStateSpeech2Sil == state_change) + { + _continous_silence_frame_count = 0; + if (_vad_state_machine == (int)VadStateMachine.kVadInStateStartPointNotDetected) + { return; } + else if (_vad_state_machine == (int)VadStateMachine.kVadInStateInSpeechSegment) + { + if (cur_frm_idx - _confirmed_start_frame + 1 > _vad_opts.max_single_segment_time / frm_shift_in_ms) + { + OnVoiceEnd(cur_frm_idx, false, false); + _vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected; + } + else if (!is_final_frame) + { + OnVoiceDetected(cur_frm_idx); + } + else + { + MaybeOnVoiceEndIfLastFrame(is_final_frame, cur_frm_idx); + } + + } + else + { + return; + } + } + else if (AudioChangeState.kChangeStateSpeech2Speech == state_change) + { + _continous_silence_frame_count = 0; + if (_vad_state_machine == (int)VadStateMachine.kVadInStateInSpeechSegment) + { + if (cur_frm_idx - _confirmed_start_frame + 1 > _vad_opts.max_single_segment_time / frm_shift_in_ms) + { + _max_time_out = true; + OnVoiceEnd(cur_frm_idx, false, false); + _vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected; + } + else if (!is_final_frame) + { + OnVoiceDetected(cur_frm_idx); + } + else + { + MaybeOnVoiceEndIfLastFrame(is_final_frame, cur_frm_idx); + } + } + else + { + return; + } + + } + else if (AudioChangeState.kChangeStateSil2Sil == state_change) + { + _continous_silence_frame_count += 1; + if (_vad_state_machine == (int)VadStateMachine.kVadInStateStartPointNotDetected) + { + // silence timeout, return zero length decision + if (((_vad_opts.detect_mode == (int)VadDetectMode.kVadSingleUtteranceDetectMode) && ( + _continous_silence_frame_count * frm_shift_in_ms > _vad_opts.max_start_silence_time)) || (is_final_frame && _number_end_time_detected == 0)) + { + for (int t = _lastest_confirmed_silence_frame + 1; t < cur_frm_idx; t++) + { + OnSilenceDetected(t); + } + OnVoiceStart(0, true); + OnVoiceEnd(0, true, false); + _vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected; + } + else + { + if (cur_frm_idx >= LatencyFrmNumAtStartPoint()) + { + OnSilenceDetected(cur_frm_idx - LatencyFrmNumAtStartPoint()); + } + } + } + else if (_vad_state_machine == (int)VadStateMachine.kVadInStateInSpeechSegment) + { + if (_continous_silence_frame_count * frm_shift_in_ms >= _max_end_sil_frame_cnt_thresh) + { + int lookback_frame = (int)(_max_end_sil_frame_cnt_thresh / frm_shift_in_ms); + if (_vad_opts.do_extend != 0) + { + lookback_frame -= (int)(_vad_opts.lookahead_time_end_point / frm_shift_in_ms); + lookback_frame -= 1; + lookback_frame = Math.Max(0, lookback_frame); + } + + OnVoiceEnd(cur_frm_idx - lookback_frame, false, false); + _vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected; + } + else if (cur_frm_idx - _confirmed_start_frame + 1 > _vad_opts.max_single_segment_time / frm_shift_in_ms) + { + OnVoiceEnd(cur_frm_idx, false, false); + _vad_state_machine = (int)VadStateMachine.kVadInStateEndPointDetected; + } + + else if (_vad_opts.do_extend != 0 && !is_final_frame) + { + if (_continous_silence_frame_count <= (int)(_vad_opts.lookahead_time_end_point / frm_shift_in_ms)) + { + OnVoiceDetected(cur_frm_idx); + } + } + + else + { + MaybeOnVoiceEndIfLastFrame(is_final_frame, cur_frm_idx); + } + } + else + { + return; + } + + } + + if (_vad_state_machine == (int)VadStateMachine.kVadInStateEndPointDetected && _vad_opts.detect_mode == (int)VadDetectMode.kVadMutipleUtteranceDetectMode) + { + ResetDetection(); + } + + } + + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Lib/kaldi-native-fbank-dll.dll b/funasr/runtime/csharp/AliFsmnVadSharp/Lib/kaldi-native-fbank-dll.dll new file mode 100644 index 0000000000000000000000000000000000000000..cddc94074c05f4d5c8d30fc84e71c660e8d64183 GIT binary patch literal 487936 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 YAQypL1ac9`MIaY}Tm*6vn0W;L2U$2C^Z)<= literal 0 HcmV?d00001 diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Model/CmvnEntity.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Model/CmvnEntity.cs new file mode 100644 index 000000000..2f93df1b2 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Model/CmvnEntity.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp.Model +{ + internal class CmvnEntity + { + private List _means = new List(); + private List _vars = new List(); + + public List Means { get => _means; set => _means = value; } + public List Vars { get => _vars; set => _vars = value; } + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Model/E2EVadFrameProbEntity.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Model/E2EVadFrameProbEntity.cs new file mode 100644 index 000000000..58a4ca9a0 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Model/E2EVadFrameProbEntity.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp.Model +{ + internal class E2EVadFrameProbEntity + { + private double _noise_prob = 0.0F; + private double _speech_prob = 0.0F; + private double _score = 0.0F; + private int _frame_id = 0; + private int _frm_state = 0; + + public double noise_prob { get => _noise_prob; set => _noise_prob = value; } + public double speech_prob { get => _speech_prob; set => _speech_prob = value; } + public double score { get => _score; set => _score = value; } + public int frame_id { get => _frame_id; set => _frame_id = value; } + public int frm_state { get => _frm_state; set => _frm_state = value; } + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Model/E2EVadSpeechBufWithDoaEntity.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Model/E2EVadSpeechBufWithDoaEntity.cs new file mode 100644 index 000000000..8c2e7f7a9 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Model/E2EVadSpeechBufWithDoaEntity.cs @@ -0,0 +1,98 @@ +// AliFsmnVadSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// AliFsmnVadSharp.Model.E2EVadSpeechBufWithDoaEntity +internal class E2EVadSpeechBufWithDoaEntity +{ + private int _start_ms = 0; + + private int _end_ms = 0; + + private byte[]? _buffer; + + private bool _contain_seg_start_point = false; + + private bool _contain_seg_end_point = false; + + private int _doa = 0; + + public int start_ms + { + get + { + return _start_ms; + } + set + { + _start_ms = value; + } + } + + public int end_ms + { + get + { + return _end_ms; + } + set + { + _end_ms = value; + } + } + + public byte[]? buffer + { + get + { + return _buffer; + } + set + { + _buffer = value; + } + } + + public bool contain_seg_start_point + { + get + { + return _contain_seg_start_point; + } + set + { + _contain_seg_start_point = value; + } + } + + public bool contain_seg_end_point + { + get + { + return _contain_seg_end_point; + } + set + { + _contain_seg_end_point = value; + } + } + + public int doa + { + get + { + return _doa; + } + set + { + _doa = value; + } + } + + public void Reset() + { + _start_ms = 0; + _end_ms = 0; + _buffer = new byte[0]; + _contain_seg_start_point = false; + _contain_seg_end_point = false; + _doa = 0; + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Model/EncoderConfEntity.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Model/EncoderConfEntity.cs new file mode 100644 index 000000000..8365b1206 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Model/EncoderConfEntity.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp.Model +{ + public class EncoderConfEntity + { + private int _input_dim=400; + private int _input_affineDim = 140; + private int _fsmn_layers = 4; + private int _linear_dim = 250; + private int _proj_dim = 128; + private int _lorder = 20; + private int _rorder = 0; + private int _lstride = 1; + private int _rstride = 0; + private int _output_dffine_dim = 140; + private int _output_dim = 248; + + public int input_dim { get => _input_dim; set => _input_dim = value; } + public int input_affine_dim { get => _input_affineDim; set => _input_affineDim = value; } + public int fsmn_layers { get => _fsmn_layers; set => _fsmn_layers = value; } + public int linear_dim { get => _linear_dim; set => _linear_dim = value; } + public int proj_dim { get => _proj_dim; set => _proj_dim = value; } + public int lorder { get => _lorder; set => _lorder = value; } + public int rorder { get => _rorder; set => _rorder = value; } + public int lstride { get => _lstride; set => _lstride = value; } + public int rstride { get => _rstride; set => _rstride = value; } + public int output_affine_dim { get => _output_dffine_dim; set => _output_dffine_dim = value; } + public int output_dim { get => _output_dim; set => _output_dim = value; } + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Model/FrontendConfEntity.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Model/FrontendConfEntity.cs new file mode 100644 index 000000000..22bb35aed --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Model/FrontendConfEntity.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp.Model +{ + public class FrontendConfEntity + { + private int _fs = 16000; + private string _window = "hamming"; + private int _n_mels = 80; + private int _frame_length = 25; + private int _frame_shift = 10; + private float _dither = 0.0F; + private int _lfr_m = 5; + private int _lfr_n = 1; + + public int fs { get => _fs; set => _fs = value; } + public string window { get => _window; set => _window = value; } + public int n_mels { get => _n_mels; set => _n_mels = value; } + public int frame_length { get => _frame_length; set => _frame_length = value; } + public int frame_shift { get => _frame_shift; set => _frame_shift = value; } + public float dither { get => _dither; set => _dither = value; } + public int lfr_m { get => _lfr_m; set => _lfr_m = value; } + public int lfr_n { get => _lfr_n; set => _lfr_n = value; } + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Model/SegmentEntity.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Model/SegmentEntity.cs new file mode 100644 index 000000000..bdb715d8d --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Model/SegmentEntity.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp.Model +{ + public class SegmentEntity + { + private List _segment=new List(); + private List _waveform=new List(); + + public List Segment { get => _segment; set => _segment = value; } + public List Waveform { get => _waveform; set => _waveform = value; } + //public SegmentEntity() + //{ + // int[] t=new int[0]; + // _segment.Add(t); + //} + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadInputEntity.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadInputEntity.cs new file mode 100644 index 000000000..fcd63d892 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadInputEntity.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp.Model +{ + internal class VadInputEntity + { + private float[]? _speech; + private int _speechLength; + private List _inCaches = new List(); + private float[]? _waveform; + private E2EVadModel _vad_scorer; + + public float[]? Speech { get => _speech; set => _speech = value; } + public int SpeechLength { get => _speechLength; set => _speechLength = value; } + public List InCaches { get => _inCaches; set => _inCaches = value; } + public float[] Waveform { get => _waveform; set => _waveform = value; } + internal E2EVadModel VadScorer { get => _vad_scorer; set => _vad_scorer = value; } + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadOutputEntity.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadOutputEntity.cs new file mode 100644 index 000000000..fa8639e61 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadOutputEntity.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp.Model +{ + internal class VadOutputEntity + { + private float[,,]? _scores; + private List _outCaches=new List(); + private float[]? _waveform; + + public float[,,]? Scores { get => _scores; set => _scores = value; } + public List OutCaches { get => _outCaches; set => _outCaches = value; } + public float[] Waveform { get => _waveform; set => _waveform = value; } + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadPostConfEntity.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadPostConfEntity.cs new file mode 100644 index 000000000..e566cf2b1 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadPostConfEntity.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp.Model +{ + public class VadPostConfEntity + { + private int _sample_rate= 16000; + private int _detect_mode = 1 ; + private int _snr_mode = 0; + private int _max_end_silence_time = 800; + private int _max_start_silence_time = 3000; + private bool _do_start_point_detection = true; + private bool _do_end_point_detection = true; + private int _window_size_ms = 200; + private int _sil_to_speech_time_thres = 150; + private int _speech_to_sil_time_thres = 150; + private float _speech_2_noise_ratio = 1.0F; + private int _do_extend = 1; + private int _lookback_time_start_point = 200; + private int _lookahead_time_end_point = 100; + private int _max_single_segment_time = 60000; + private int _nn_eval_block_size = 8; + private int _dcd_block_size = 4; + private float _snr_thres = -100.0F; + private int _noise_frame_num_used_for_snr = 100; + private float _decibel_thres = -100.0F; + private float _speech_noise_thres = 0.6F; + private float _fe_prior_thres = 0.0001F; + private int _silence_pdf_num = 1; + private int[] _sil_pdf_ids = new int[] {0}; + private float _speech_noise_thresh_low = -0.1F; + private float _speech_noise_thresh_high = 0.3F; + private bool _output_frame_probs = false; + private int _frame_in_ms = 10; + private int _frame_length_ms = 25; + + public int sample_rate { get => _sample_rate; set => _sample_rate = value; } + public int detect_mode { get => _detect_mode; set => _detect_mode = value; } + public int snr_mode { get => _snr_mode; set => _snr_mode = value; } + public int max_end_silence_time { get => _max_end_silence_time; set => _max_end_silence_time = value; } + public int max_start_silence_time { get => _max_start_silence_time; set => _max_start_silence_time = value; } + public bool do_start_point_detection { get => _do_start_point_detection; set => _do_start_point_detection = value; } + public bool do_end_point_detection { get => _do_end_point_detection; set => _do_end_point_detection = value; } + public int window_size_ms { get => _window_size_ms; set => _window_size_ms = value; } + public int sil_to_speech_time_thres { get => _sil_to_speech_time_thres; set => _sil_to_speech_time_thres = value; } + public int speech_to_sil_time_thres { get => _speech_to_sil_time_thres; set => _speech_to_sil_time_thres = value; } + public float speech_2_noise_ratio { get => _speech_2_noise_ratio; set => _speech_2_noise_ratio = value; } + public int do_extend { get => _do_extend; set => _do_extend = value; } + public int lookback_time_start_point { get => _lookback_time_start_point; set => _lookback_time_start_point = value; } + public int lookahead_time_end_point { get => _lookahead_time_end_point; set => _lookahead_time_end_point = value; } + public int max_single_segment_time { get => _max_single_segment_time; set => _max_single_segment_time = value; } + public int nn_eval_block_size { get => _nn_eval_block_size; set => _nn_eval_block_size = value; } + public int dcd_block_size { get => _dcd_block_size; set => _dcd_block_size = value; } + public float snr_thres { get => _snr_thres; set => _snr_thres = value; } + public int noise_frame_num_used_for_snr { get => _noise_frame_num_used_for_snr; set => _noise_frame_num_used_for_snr = value; } + public float decibel_thres { get => _decibel_thres; set => _decibel_thres = value; } + public float speech_noise_thres { get => _speech_noise_thres; set => _speech_noise_thres = value; } + public float fe_prior_thres { get => _fe_prior_thres; set => _fe_prior_thres = value; } + public int silence_pdf_num { get => _silence_pdf_num; set => _silence_pdf_num = value; } + public int[] sil_pdf_ids { get => _sil_pdf_ids; set => _sil_pdf_ids = value; } + public float speech_noise_thresh_low { get => _speech_noise_thresh_low; set => _speech_noise_thresh_low = value; } + public float speech_noise_thresh_high { get => _speech_noise_thresh_high; set => _speech_noise_thresh_high = value; } + public bool output_frame_probs { get => _output_frame_probs; set => _output_frame_probs = value; } + public int frame_in_ms { get => _frame_in_ms; set => _frame_in_ms = value; } + public int frame_length_ms { get => _frame_length_ms; set => _frame_length_ms = value; } + + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadYamlEntity.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadYamlEntity.cs new file mode 100644 index 000000000..65e77eda8 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Model/VadYamlEntity.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp.Model +{ + internal class VadYamlEntity + { + private int _input_size; + private string _frontend = "wav_frontend"; + private FrontendConfEntity _frontend_conf=new FrontendConfEntity(); + private string _model = "e2evad"; + private string _encoder = "fsmn"; + private EncoderConfEntity _encoder_conf=new EncoderConfEntity(); + private VadPostConfEntity _vad_post_conf=new VadPostConfEntity(); + + public int input_size { get => _input_size; set => _input_size = value; } + public string frontend { get => _frontend; set => _frontend = value; } + public string model { get => _model; set => _model = value; } + public string encoder { get => _encoder; set => _encoder = value; } + public FrontendConfEntity frontend_conf { get => _frontend_conf; set => _frontend_conf = value; } + public EncoderConfEntity encoder_conf { get => _encoder_conf; set => _encoder_conf = value; } + public VadPostConfEntity vad_post_conf { get => _vad_post_conf; set => _vad_post_conf = value; } + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Struct/FbankData.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Struct/FbankData.cs new file mode 100644 index 000000000..bbad3dc95 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Struct/FbankData.cs @@ -0,0 +1,6 @@ +using System.Runtime.InteropServices; + +namespace AliFsmnVadSharp.Struct +{ + +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/Utils/YamlHelper.cs b/funasr/runtime/csharp/AliFsmnVadSharp/Utils/YamlHelper.cs new file mode 100644 index 000000000..0b460ff16 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/Utils/YamlHelper.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.Json; +using YamlDotNet.Serialization; + +namespace AliFsmnVadSharp.Utils +{ + internal class YamlHelper + { + public static T ReadYaml(string yamlFilePath) + { + if (!File.Exists(yamlFilePath)) + { +#pragma warning disable CS8603 // 可能返回 null 引用。 + return default(T); +#pragma warning restore CS8603 // 可能返回 null 引用。 + } + StreamReader yamlReader = File.OpenText(yamlFilePath); + Deserializer yamlDeserializer = new Deserializer(); + T info = yamlDeserializer.Deserialize(yamlReader); + yamlReader.Close(); + return info; + } + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/WavFrontend.cs b/funasr/runtime/csharp/AliFsmnVadSharp/WavFrontend.cs new file mode 100644 index 000000000..2c5b50fbb --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/WavFrontend.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AliFsmnVadSharp.Model; +using AliFsmnVadSharp.DLL; +using AliFsmnVadSharp.Struct; +using System.Runtime.InteropServices; + +namespace AliFsmnVadSharp +{ + internal class WavFrontend + { + private string _mvnFilePath; + private FrontendConfEntity _frontendConfEntity; + IntPtr _opts = IntPtr.Zero; + private CmvnEntity _cmvnEntity; + + private static int _fbank_beg_idx = 0; + + public WavFrontend(string mvnFilePath, FrontendConfEntity frontendConfEntity) + { + _mvnFilePath = mvnFilePath; + _frontendConfEntity = frontendConfEntity; + _fbank_beg_idx = 0; + _opts = KaldiNativeFbank.GetFbankOptions( + dither: _frontendConfEntity.dither, + snip_edges: true, + sample_rate: _frontendConfEntity.fs, + num_bins: _frontendConfEntity.n_mels + ); + _cmvnEntity = LoadCmvn(mvnFilePath); + } + + public float[] GetFbank(float[] samples) + { + float sample_rate = _frontendConfEntity.fs; + samples = samples.Select((float x) => x * 32768f).ToArray(); + // method1 + //FbankDatas fbankDatas = new FbankDatas(); + //KaldiNativeFbank.GetFbanks(_knfOnlineFbank, framesNum,ref fbankDatas); + // method2 + KnfOnlineFbank _knfOnlineFbank = KaldiNativeFbank.GetOnlineFbank(_opts); + KaldiNativeFbank.AcceptWaveform(_knfOnlineFbank, sample_rate, samples, samples.Length); + KaldiNativeFbank.InputFinished(_knfOnlineFbank); + int framesNum = KaldiNativeFbank.GetNumFramesReady(_knfOnlineFbank); + float[] fbanks = new float[framesNum * 80]; + for (int i = 0; i < framesNum; i++) + { + FbankData fbankData = new FbankData(); + KaldiNativeFbank.GetFbank(_knfOnlineFbank, i, ref fbankData); + float[] _fbankData = new float[fbankData.data_length]; + Marshal.Copy(fbankData.data, _fbankData, 0, fbankData.data_length); + Array.Copy(_fbankData, 0, fbanks, i * 80, _fbankData.Length); + fbankData.data = IntPtr.Zero; + _fbankData = null; + } + + samples = null; + GC.Collect(); + return fbanks; + } + + + public float[] LfrCmvn(float[] fbanks) + { + float[] features = fbanks; + if (_frontendConfEntity.lfr_m != 1 || _frontendConfEntity.lfr_n != 1) + { + features = ApplyLfr(fbanks, _frontendConfEntity.lfr_m, _frontendConfEntity.lfr_n); + } + if (_cmvnEntity != null) + { + features = ApplyCmvn(features); + } + return features; + } + + private float[] ApplyCmvn(float[] inputs) + { + var arr_neg_mean = _cmvnEntity.Means; + float[] neg_mean = arr_neg_mean.Select(x => (float)Convert.ToDouble(x)).ToArray(); + var arr_inv_stddev = _cmvnEntity.Vars; + float[] inv_stddev = arr_inv_stddev.Select(x => (float)Convert.ToDouble(x)).ToArray(); + + int dim = neg_mean.Length; + int num_frames = inputs.Length / dim; + + for (int i = 0; i < num_frames; i++) + { + for (int k = 0; k != dim; ++k) + { + inputs[dim * i + k] = (inputs[dim * i + k] + neg_mean[k]) * inv_stddev[k]; + } + } + return inputs; + } + + public float[] ApplyLfr(float[] inputs, int lfr_m, int lfr_n) + { + int t = inputs.Length / 80; + int t_lfr = (int)Math.Floor((double)(t / lfr_n)); + float[] input_0 = new float[80]; + Array.Copy(inputs, 0, input_0, 0, 80); + int tile_x = (lfr_m - 1) / 2; + t = t + tile_x; + float[] inputs_temp = new float[t * 80]; + for (int i = 0; i < tile_x; i++) + { + Array.Copy(input_0, 0, inputs_temp, tile_x * 80, 80); + } + Array.Copy(inputs, 0, inputs_temp, tile_x * 80, inputs.Length); + inputs = inputs_temp; + + float[] LFR_outputs = new float[t_lfr * lfr_m * 80]; + for (int i = 0; i < t_lfr; i++) + { + if (lfr_m <= t - i * lfr_n) + { + Array.Copy(inputs, i * lfr_n * 80, LFR_outputs, i* lfr_m * 80, lfr_m * 80); + } + else + { + // process last LFR frame + int num_padding = lfr_m - (t - i * lfr_n); + float[] frame = new float[lfr_m * 80]; + Array.Copy(inputs, i * lfr_n * 80, frame, 0, (t - i * lfr_n) * 80); + + for (int j = 0; j < num_padding; j++) + { + Array.Copy(inputs, (t - 1) * 80, frame, (lfr_m - num_padding + j) * 80, 80); + } + Array.Copy(frame, 0, LFR_outputs, i * lfr_m * 80, frame.Length); + } + } + return LFR_outputs; + } + + private CmvnEntity LoadCmvn(string mvnFilePath) + { + List means_list = new List(); + List vars_list = new List(); + FileStreamOptions options = new FileStreamOptions(); + options.Access = FileAccess.Read; + options.Mode = FileMode.Open; + StreamReader srtReader = new StreamReader(mvnFilePath, options); + int i = 0; + while (!srtReader.EndOfStream) + { + string? strLine = srtReader.ReadLine(); + if (!string.IsNullOrEmpty(strLine)) + { + if (strLine.StartsWith("")) + { + i=1; + continue; + } + if (strLine.StartsWith("")) + { + i = 2; + continue; + } + if (strLine.StartsWith("") && i==1) + { + string[] add_shift_line = strLine.Substring(strLine.IndexOf("[") + 1, strLine.LastIndexOf("]") - strLine.IndexOf("[") - 1).Split(" "); + means_list = add_shift_line.Where(x => !string.IsNullOrEmpty(x)).Select(x => float.Parse(x.Trim())).ToList(); + continue; + } + if (strLine.StartsWith("") && i==2) + { + string[] rescale_line = strLine.Substring(strLine.IndexOf("[") + 1, strLine.LastIndexOf("]") - strLine.IndexOf("[") - 1).Split(" "); + vars_list = rescale_line.Where(x => !string.IsNullOrEmpty(x)).Select(x => float.Parse(x.Trim())).ToList(); + continue; + } + } + } + CmvnEntity cmvnEntity = new CmvnEntity(); + cmvnEntity.Means = means_list; + cmvnEntity.Vars = vars_list; + return cmvnEntity; + } + + } +} diff --git a/funasr/runtime/csharp/AliFsmnVadSharp/WindowDetector.cs b/funasr/runtime/csharp/AliFsmnVadSharp/WindowDetector.cs new file mode 100644 index 000000000..785af32b2 --- /dev/null +++ b/funasr/runtime/csharp/AliFsmnVadSharp/WindowDetector.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AliFsmnVadSharp +{ + public enum FrameState + { + kFrameStateInvalid = -1, + kFrameStateSpeech = 1, + kFrameStateSil = 0 + } + + /// + /// final voice/unvoice state per frame + /// + public enum AudioChangeState + { + kChangeStateSpeech2Speech = 0, + kChangeStateSpeech2Sil = 1, + kChangeStateSil2Sil = 2, + kChangeStateSil2Speech = 3, + kChangeStateNoBegin = 4, + kChangeStateInvalid = 5 + } + + + internal class WindowDetector + { + private int _window_size_ms = 0; //window_size_ms; + private int _sil_to_speech_time = 0; //sil_to_speech_time; + private int _speech_to_sil_time = 0; //speech_to_sil_time; + private int _frame_size_ms = 0; //frame_size_ms; + + private int _win_size_frame = 0; + private int _win_sum = 0; + private int[] _win_state = new int[0];// * _win_size_frame; // 初始化窗 + + private int _cur_win_pos = 0; + private int _pre_frame_state = (int)FrameState.kFrameStateSil; + private int _cur_frame_state = (int)FrameState.kFrameStateSil; + private int _sil_to_speech_frmcnt_thres = 0; //int(sil_to_speech_time / frame_size_ms); + private int _speech_to_sil_frmcnt_thres = 0; //int(speech_to_sil_time / frame_size_ms); + + private int _voice_last_frame_count = 0; + private int _noise_last_frame_count = 0; + private int _hydre_frame_count = 0; + + public WindowDetector() + { + + } + + public WindowDetector(int window_size_ms, int sil_to_speech_time, int speech_to_sil_time, int frame_size_ms) + { + _window_size_ms = window_size_ms; + _sil_to_speech_time = sil_to_speech_time; + _speech_to_sil_time = speech_to_sil_time; + _frame_size_ms = frame_size_ms; + + _win_size_frame = (int)(window_size_ms / frame_size_ms); + _win_sum = 0; + _win_state = new int[_win_size_frame];//[0] * _win_size_frame; // 初始化窗 + + _cur_win_pos = 0; + _pre_frame_state = (int)FrameState.kFrameStateSil; + _cur_frame_state = (int)FrameState.kFrameStateSil; + _sil_to_speech_frmcnt_thres = (int)(sil_to_speech_time / frame_size_ms); + _speech_to_sil_frmcnt_thres = (int)(speech_to_sil_time / frame_size_ms); + + _voice_last_frame_count = 0; + _noise_last_frame_count = 0; + _hydre_frame_count = 0; + } + + public void Reset() + { + _cur_win_pos = 0; + _win_sum = 0; + _win_state = new int[_win_size_frame]; + _pre_frame_state = (int)FrameState.kFrameStateSil; + _cur_frame_state = (int)FrameState.kFrameStateSil; + _voice_last_frame_count = 0; + _noise_last_frame_count = 0; + _hydre_frame_count = 0; + } + + + public int GetWinSize() + { + return _win_size_frame; + } + + public AudioChangeState DetectOneFrame(FrameState frameState, int frame_count) + { + + + _cur_frame_state = (int)FrameState.kFrameStateSil; + if (frameState == FrameState.kFrameStateSpeech) + { + _cur_frame_state = 1; + } + + else if (frameState == FrameState.kFrameStateSil) + { + _cur_frame_state = 0; + } + + else + { + return AudioChangeState.kChangeStateInvalid; + } + + _win_sum -= _win_state[_cur_win_pos]; + _win_sum += _cur_frame_state; + _win_state[_cur_win_pos] = _cur_frame_state; + _cur_win_pos = (_cur_win_pos + 1) % _win_size_frame; + + if (_pre_frame_state == (int)FrameState.kFrameStateSil && _win_sum >= _sil_to_speech_frmcnt_thres) + { + _pre_frame_state = (int)FrameState.kFrameStateSpeech; + return AudioChangeState.kChangeStateSil2Speech; + } + + + if (_pre_frame_state == (int)FrameState.kFrameStateSpeech && _win_sum <= _speech_to_sil_frmcnt_thres) + { + _pre_frame_state = (int)FrameState.kFrameStateSil; + return AudioChangeState.kChangeStateSpeech2Sil; + } + + + if (_pre_frame_state == (int)FrameState.kFrameStateSil) + { + return AudioChangeState.kChangeStateSil2Sil; + } + + if (_pre_frame_state == (int)FrameState.kFrameStateSpeech) + { + return AudioChangeState.kChangeStateSpeech2Speech; + } + + return AudioChangeState.kChangeStateInvalid; + } + + private int FrameSizeMs() + { + return _frame_size_ms; + } + + + + } +} diff --git a/funasr/runtime/csharp/README.md b/funasr/runtime/csharp/README.md new file mode 100644 index 000000000..68175cd5a --- /dev/null +++ b/funasr/runtime/csharp/README.md @@ -0,0 +1,59 @@ +# AliFsmnVadSharp +##### 简介: +项目中使用的VAD模型是阿里巴巴达摩院提供的FSMN-Monophone VAD模型。 +**项目基于Net 6.0,使用C#编写,调用Microsoft.ML.OnnxRuntime对onnx模型进行解码,支持跨平台编译。项目以库的形式进行调用,部署非常方便。** +VAD整体流程的rtf在0.008左右。 + +##### 用途: +16k中文通用VAD模型:可用于检测长语音片段中有效语音的起止时间点. +FSMN-Monophone VAD是达摩院语音团队提出的高效语音端点检测模型,用于检测输入音频中有效语音的起止时间点信息,并将检测出来的有效音频片段输入识别引擎进行识别,减少无效语音带来的识别错误。 + +##### VAD常用参数调整说明(参考:vad.yaml文件): +max_end_silence_time:尾部连续检测到多长时间静音进行尾点判停,参数范围500ms~6000ms,默认值800ms(该值过低容易出现语音提前截断的情况)。 +speech_noise_thres:speech的得分减去noise的得分大于此值则判断为speech,参数范围:(-1,1) +取值越趋于-1,噪音被误判定为语音的概率越大,FA越高 +取值越趋于+1,语音被误判定为噪音的概率越大,Pmiss越高 +通常情况下,该值会根据当前模型在长语音测试集上的效果取balance + +##### 模型获取 + +##### 调用方式: +###### 1.添加项目引用 +using AliFsmnVadSharp; + +###### 2.初始化模型和配置 +```csharp +string applicationBase = AppDomain.CurrentDomain.BaseDirectory; +string modelFilePath = applicationBase + "./speech_fsmn_vad_zh-cn-16k-common-pytorch/model.onnx"; +string configFilePath = applicationBase + "./speech_fsmn_vad_zh-cn-16k-common-pytorch/vad.yaml"; +string mvnFilePath = applicationBase + "./speech_fsmn_vad_zh-cn-16k-common-pytorch/vad.mvn"; +int batchSize = 2;//批量解码 +AliFsmnVad aliFsmnVad = new AliFsmnVad(modelFilePath, configFilePath, mvnFilePath, batchSize); +``` +###### 3.调用 +方法一(适用于小文件): +```csharp +SegmentEntity[] segments_duration = aliFsmnVad.GetSegments(samples); +``` +方法二(适用于大文件): +```csharp +SegmentEntity[] segments_duration = aliFsmnVad.GetSegmentsByStep(samples); +``` +###### 4.输出结果: +``` +load model and init config elapsed_milliseconds:463.5390625 +vad infer result: +[[70,2340][2620,6200][6480,23670][23950,26250][26780,28990][29950,31430][31750,37600][38210,46900][47310,49630][49910,56460][56740,59540][59820,70450]] +elapsed_milliseconds:662.796875 +total_duration:70470.625 +rtf:0.009405292985552491 +``` +输出的数据,例如:[70,2340],是以毫秒为单位的segement的起止时间,可以以此为依据对音频进行分片。其中静音噪音部分已被去除。 + +其他说明: +测试用例:AliFsmnVadSharp.Examples。 +测试环境:windows11。 +测试用例中samples的计算,使用的是NAudio库。 + +通过以下链接了解更多: +https://www.modelscope.cn/models/damo/speech_fsmn_vad_zh-cn-16k-common-pytorch/summary