first commit for takway.ai
This commit is contained in:
commit
d7a6de771c
3256 files changed
+795173
No files matched your search
@@ -0,0 +1 @@
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("LivePlayer",[],t):"object"==typeof exports?exports.LivePlayer=t():e.LivePlayer=t()}(window,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}({0:function(e,t,n){e.exports=n("qtAY")},"2SVd":function(e,t,n){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},"5oMp":function(e,t,n){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},"8MIm":function(e,t,n){(e.exports=n("I1BE")(void 0)).push([e.i,'.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-modal-dialog,.vjs-button>.vjs-icon-placeholder:before,.vjs-modal-dialog .vjs-modal-dialog-content{position:absolute;top:0;left:0;width:100%;height:100%}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.vjs-button>.vjs-icon-placeholder:before{text-align:center}@font-face{font-family:VideoJS;src:url(data:application/font-woff;charset=utf-8;base64,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 truncated
|
||||
@@ -0,0 +1 @@
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("global/window"),require("global/document")):"function"==typeof define&&define.amd?define(["global/window","global/document"],t):(e=e||self).videojs=t(e.window,e.document)}(this,function(e,t){e=e&&e.hasOwnProperty("default")?e.default:e,t=t&&t.hasOwnProperty("default")?t.default:t;var i="7.6.3",n=[],r=function(t,i){return function(r,a,s){var o=i.levels[a],u=new RegExp("^("+o+")$");if("log"!==r&&s.unshift(r.toUpperCase()+":"),s.unshift(t+":"),n&&n.push([].concat(s)),e.console){var l=e.console[r];l||"debug"!==r||(l=e.console.info||e.console.log),l&&o&&u.test(r)&&l[Array.isArray(s)?"apply":"call"](e.console,s)}}};var a=function e(t){var i,a="info",s=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];i("log",a,t)};return i=r(t,s),s.createLogger=function(i){return e(t+": "+i)},s.levels={all:"debug|log|warn|error",off:"",debug:"debug|log|warn|error",info:"log|warn|error",warn:"warn|error",error:"error",DEFAULT:a},s.level=function(e){if("string"==typeof e){if(!s.levels.hasOwnProperty(e))throw new Error('"'+e+'" in not a valid log level');a=e}return a},(s.history=function(){return n?[].concat(n):[]}).filter=function(e){return(n||[]).filter(function(t){return new RegExp(".*"+e+".*").test(t[0])})},s.history.clear=function(){n&&(n.length=0)},s.history.disable=function(){null!==n&&(n.length=0,n=null)},s.history.enable=function(){null===n&&(n=[])},s.error=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return i("error",a,t)},s.warn=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return i("warn",a,t)},s.debug=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return i("debug",a,t)},s}("VIDEOJS"),s=a.createLogger,o=Object.prototype.toString,u=function(e){return c(e)?Object.keys(e):[]};function l(e,t){u(e).forEach(function(i){return t(e[i],i)})}function d(e){for(var t=arguments.length,i=new Array(t>1?t-1:0),n=1;n<t;n++)i[n-1]=arguments[n];return Object.assign?Object.assign.apply(Object,[e].concat(i)):(i.forEach(function(t){t&&l(t,function(t,i){e[i]=t})}),e)}function c(e){return!!e&&"object"==typeof e}function h(e){return c(e)&&"[object Object]"===o.call(e)&&e.constructor===Object}function f(t,i){if(!t||!i)return"";if("function"==typeof e.getComputedStyle){var n=e.getComputedStyle(t);return n?n.getPropertyValue(i)||n[i]:""}return""}function p(e){return"string"==typeof e&&/\S/.test(e)}function m(e){if(/\s/.test(e))throw new Error("class has illegal whitespace characters")}function g(){return t===e.document}function y(e){return c(e)&&1===e.nodeType}function v(){try{return e.parent!==e.self}catch(e){return!0}}function _(e){return function(i,n){if(!p(i))return t[e](null);p(n)&&(n=t.querySelector(n));var r=y(n)?n:t;return r[e]&&r[e](i)}}function b(e,i,n,r){void 0===e&&(e="div"),void 0===i&&(i={}),void 0===n&&(n={});var s=t.createElement(e);return Object.getOwnPropertyNames(i).forEach(function(e){var t=i[e];-1!==e.indexOf("aria-")||"role"===e||"type"===e?(a.warn("Setting attributes in the second argument of createEl()\nhas been deprecated. Use the third argument instead.\ncreateEl(type, properties, attributes). Attempting to set "+e+" to "+t+"."),s.setAttribute(e,t)):"textContent"===e?T(s,t):s[e]=t}),Object.getOwnPropertyNames(n).forEach(function(e){s.setAttribute(e,n[e])}),r&&F(s,r),s}function T(e,t){return void 0===e.textContent?e.innerText=t:e.textContent=t,e}function S(e,t){t.firstChild?t.insertBefore(e,t.firstChild):t.appendChild(e)}function k(e,t){return m(t),e.classList?e.classList.contains(t):(i=t,new RegExp("(^|\\s)"+i+"($|\\s)")).test(e.className);var i}function w(e,t){return e?(e.classList?e.classList.add(t):k(e,t)||(e.className=(e.className+" "+t).trim()),e):e}function E(e,t){return e?(e.classList?e.classList.remove(t):(m(t),e.className=e.className.split(/\s+/).filter(function(e){return e!==t}).join(" ")),e):e}function C(e,t,i){var n=k(e,t);return"function"==typeof i&&(i=i(e,t)),"boolean"!=typeof i&&(i=!n),i===n?e:(i?w(e,t):E(e,t),e)}function A(e,t){Object.getOwnPropertyNames(t).forEach(function(i){var n=t[i];null===n||void 0===n||!1===n?e.removeAttribute(i):e.setAttribute(i,!0===n?"":n)})}function L(e){var t={},i=",autoplay,stretch,fluent,controls,playsinline,loop,muted,default,defaultMuted,";if(e&&e.attributes&&e.attributes.length>0)for(var n=e.attributes,r=n.length-1;r>=0;r--){var a=n[r].name,s=n[r].value;"boolean"!=typeof e[a]&&-1===i.indexOf(","+a+",")||(s=null!==s),t[a]=s}return t}function O(e,t){return e.getAttribute(t)}function P(e,t,i){e.setAttribute(t,i)}function I(e,t){e.removeAttribute(t)}function R(){t.body.focus(),t.onselectstart=function(){return!1}}function x(){t.onselectstart=function(){return!0}}function D(e){if(e&&e.getBoundingClientRect&&e.parentNode){var t=e.getBoundingClientRect(),i={};return["bottom","height","left","right","top","width"].forEach(function(e){void 0!==t[e]Line truncated
|
||||
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>语音识别</title>
|
||||
|
||||
</head>
|
||||
<body style="margin-left: 3%">
|
||||
<script src="recorder-core.js" charset="UTF-8"></script>
|
||||
<script src="wav.js" charset="UTF-8"></script>
|
||||
<script src="pcm.js" charset="UTF-8"></script>
|
||||
|
||||
|
||||
|
||||
<h1>FunASR Demo</h1>
|
||||
<h3>这里是FunASR开源项目体验demo,集成了VAD、ASR与标点等工业级别的模型,支持长音频离线文件转写,实时语音识别等,开源项目地址:https://github.com/alibaba-damo-academy/FunASR</h3>
|
||||
|
||||
<div class="div_class_topArea">
|
||||
|
||||
<div class="div_class_recordControl">
|
||||
asr服务器地址(必填):
|
||||
<br>
|
||||
<input id="wssip" type="text" onchange="addresschange()" style=" width: 100%;height:100%" value="wss://127.0.0.1:10095/"/>
|
||||
<br>
|
||||
<a id="wsslink" href="#" onclick="window.open('https://127.0.0.1:10095/', '_blank')"><div id="info_wslink">点此处手工授权wss://127.0.0.1:10095/</div></a>
|
||||
<br>
|
||||
<br>
|
||||
<div style="border:2px solid #ccc;">
|
||||
选择录音模式:<br/>
|
||||
|
||||
<label><input name="recoder_mode" onclick="on_recoder_mode_change()" type="radio" value="mic" checked="true"/>麦克风 </label>
|
||||
<label><input name="recoder_mode" onclick="on_recoder_mode_change()" type="radio" value="file" />文件 </label>
|
||||
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div id="mic_mode_div" style="border:2px solid #ccc;display:block;">
|
||||
选择asr模型模式:<br/>
|
||||
|
||||
<label><input name="asr_mode" type="radio" value="2pass" checked="true"/>2pass </label>
|
||||
<label><input name="asr_mode" type="radio" value="online" />online </label>
|
||||
<label><input name="asr_mode" type="radio" value="offline" />offline </label>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="rec_mode_div" style="border:2px solid #ccc;display:none;">
|
||||
|
||||
|
||||
<input type="file" id="upfile">
|
||||
|
||||
</div>
|
||||
<br>
|
||||
<div id="use_itn_div" style="border:2px solid #ccc;display:block;">
|
||||
逆文本标准化(ITN):<br/>
|
||||
<label><input name="use_itn" type="radio" value="false" checked="true"/>否 </label>
|
||||
<label><input name="use_itn" type="radio" value="true" />是 </label>
|
||||
</div>
|
||||
<br>
|
||||
<div style="border:2px solid #ccc;">
|
||||
热词设置(一行一个关键字,空格隔开权重,如"阿里巴巴 20"):
|
||||
<br>
|
||||
|
||||
|
||||
<textarea rows="3" id="varHot" style=" width: 100%;height:100%" >阿里巴巴 20 hello world 40</textarea>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
语音识别结果显示:
|
||||
<br>
|
||||
|
||||
<textarea rows="10" id="varArea" readonly="true" style=" width: 100%;height:100%" ></textarea>
|
||||
<br>
|
||||
<div id="info_div">请点击开始</div>
|
||||
<div class="div_class_buttons">
|
||||
<button id="btnConnect">连接</button>
|
||||
<button id="btnStart">开始</button>
|
||||
<button id="btnStop">停止</button>
|
||||
|
||||
</div>
|
||||
|
||||
<audio id="audio_record" type="audio/wav" controls style="margin-top: 12px; width: 100%;"></audio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="wsconnecter.js" charset="utf-8"></script>
|
||||
<script src="main.js" charset="utf-8"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,583 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2022-2023 by zhaoming,mali aihealthx.com */
|
||||
|
||||
|
||||
// 连接; 定义socket连接类对象与语音对象
|
||||
var wsconnecter = new WebSocketConnectMethod({msgHandle:getJsonMessage,stateHandle:getConnState});
|
||||
var audioBlob;
|
||||
|
||||
// 录音; 定义录音对象,wav格式
|
||||
var rec = Recorder({
|
||||
type:"pcm",
|
||||
bitRate:16,
|
||||
sampleRate:16000,
|
||||
onProcess:recProcess
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
var sampleBuf=new Int16Array();
|
||||
// 定义按钮响应事件
|
||||
var btnStart = document.getElementById('btnStart');
|
||||
btnStart.onclick = record;
|
||||
var btnStop = document.getElementById('btnStop');
|
||||
btnStop.onclick = stop;
|
||||
btnStop.disabled = true;
|
||||
btnStart.disabled = true;
|
||||
|
||||
btnConnect= document.getElementById('btnConnect');
|
||||
btnConnect.onclick = start;
|
||||
|
||||
var awsslink= document.getElementById('wsslink');
|
||||
|
||||
|
||||
var rec_text=""; // for online rec asr result
|
||||
var offline_text=""; // for offline rec asr result
|
||||
var info_div = document.getElementById('info_div');
|
||||
|
||||
var upfile = document.getElementById('upfile');
|
||||
|
||||
|
||||
|
||||
var isfilemode=false; // if it is in file mode
|
||||
var file_ext="";
|
||||
var file_sample_rate=16000; //for wav file sample rate
|
||||
var file_data_array; // array to save file data
|
||||
|
||||
var totalsend=0;
|
||||
|
||||
|
||||
var now_ipaddress=window.location.href;
|
||||
now_ipaddress=now_ipaddress.replace("https://","wss://");
|
||||
now_ipaddress=now_ipaddress.replace("static/offline/index.html","");
|
||||
var localport=window.location.port;
|
||||
if (localport == '')
|
||||
{
|
||||
now_ipaddress=now_ipaddress.substring(0, now_ipaddress.length - 1) + ':10095' + now_ipaddress.substring(now_ipaddress.length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
now_ipaddress=now_ipaddress.replace(localport,"10095");
|
||||
}
|
||||
document.getElementById('wssip').value=now_ipaddress;
|
||||
addresschange();
|
||||
function addresschange()
|
||||
{
|
||||
|
||||
var Uri = document.getElementById('wssip').value;
|
||||
document.getElementById('info_wslink').innerHTML="点此处手工授权(IOS手机)";
|
||||
Uri=Uri.replace(/wss/g,"https");
|
||||
console.log("addresschange uri=",Uri);
|
||||
|
||||
awsslink.onclick=function(){
|
||||
window.open(Uri, '_blank');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
upfile.onclick=function()
|
||||
{
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=false;
|
||||
|
||||
}
|
||||
|
||||
// from https://github.com/xiangyuecn/Recorder/tree/master
|
||||
var readWavInfo=function(bytes){
|
||||
//读取wav文件头,统一成44字节的头
|
||||
if(bytes.byteLength<44){
|
||||
return null;
|
||||
};
|
||||
var wavView=bytes;
|
||||
var eq=function(p,s){
|
||||
for(var i=0;i<s.length;i++){
|
||||
if(wavView[p+i]!=s.charCodeAt(i)){
|
||||
return false;
|
||||
};
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
if(eq(0,"RIFF")&&eq(8,"WAVEfmt ")){
|
||||
|
||||
var numCh=wavView[22];
|
||||
if(wavView[20]==1 && (numCh==1||numCh==2)){//raw pcm 单或双声道
|
||||
var sampleRate=wavView[24]+(wavView[25]<<8)+(wavView[26]<<16)+(wavView[27]<<24);
|
||||
var bitRate=wavView[34]+(wavView[35]<<8);
|
||||
var heads=[wavView.subarray(0,12)],headSize=12;//head只保留必要的块
|
||||
//搜索data块的位置
|
||||
var dataPos=0; // 44 或有更多块
|
||||
for(var i=12,iL=wavView.length-8;i<iL;){
|
||||
if(wavView[i]==100&&wavView[i+1]==97&&wavView[i+2]==116&&wavView[i+3]==97){//eq(i,"data")
|
||||
heads.push(wavView.subarray(i,i+8));
|
||||
headSize+=8;
|
||||
dataPos=i+8;break;
|
||||
}
|
||||
var i0=i;
|
||||
i+=4;
|
||||
i+=4+wavView[i]+(wavView[i+1]<<8)+(wavView[i+2]<<16)+(wavView[i+3]<<24);
|
||||
if(i0==12){//fmt
|
||||
heads.push(wavView.subarray(i0,i));
|
||||
headSize+=i-i0;
|
||||
}
|
||||
}
|
||||
if(dataPos){
|
||||
var wavHead=new Uint8Array(headSize);
|
||||
for(var i=0,n=0;i<heads.length;i++){
|
||||
wavHead.set(heads[i],n);n+=heads[i].length;
|
||||
}
|
||||
return {
|
||||
sampleRate:sampleRate
|
||||
,bitRate:bitRate
|
||||
,numChannels:numCh
|
||||
,wavHead44:wavHead
|
||||
,dataPos:dataPos
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return null;
|
||||
};
|
||||
|
||||
upfile.onchange = function () {
|
||||
var len = this.files.length;
|
||||
for(let i = 0; i < len; i++) {
|
||||
|
||||
let fileAudio = new FileReader();
|
||||
fileAudio.readAsArrayBuffer(this.files[i]);
|
||||
|
||||
file_ext=this.files[i].name.split('.').pop().toLowerCase();
|
||||
var audioblob;
|
||||
fileAudio.onload = function() {
|
||||
audioblob = fileAudio.result;
|
||||
|
||||
|
||||
file_data_array=audioblob;
|
||||
|
||||
|
||||
info_div.innerHTML='请点击连接进行识别';
|
||||
|
||||
}
|
||||
|
||||
fileAudio.onerror = function(e) {
|
||||
console.log('error' + e);
|
||||
}
|
||||
}
|
||||
// for wav file, we get the sample rate
|
||||
if(file_ext=="wav")
|
||||
for(let i = 0; i < len; i++) {
|
||||
|
||||
let fileAudio = new FileReader();
|
||||
fileAudio.readAsArrayBuffer(this.files[i]);
|
||||
fileAudio.onload = function() {
|
||||
audioblob = new Uint8Array(fileAudio.result);
|
||||
|
||||
// for wav file, we can get the sample rate
|
||||
var info=readWavInfo(audioblob);
|
||||
console.log(info);
|
||||
file_sample_rate=info.sampleRate;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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(); //not auto play
|
||||
}
|
||||
function start_file_send()
|
||||
{
|
||||
sampleBuf=new Uint8Array( 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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
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';
|
||||
|
||||
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=false;
|
||||
isfilemode=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById("mic_mode_div").style.display = 'none';
|
||||
document.getElementById("rec_mode_div").style.display = 'block';
|
||||
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=true;
|
||||
isfilemode=true;
|
||||
info_div.innerHTML='请点击选择文件';
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getHotwords(){
|
||||
|
||||
var obj = document.getElementById("varHot");
|
||||
|
||||
if(typeof(obj) == 'undefined' || obj==null || obj.value.length<=0){
|
||||
return null;
|
||||
}
|
||||
let val = obj.value.toString();
|
||||
|
||||
console.log("hotwords="+val);
|
||||
let items = val.split(/[(\r\n)\r\n]+/); //split by \r\n
|
||||
var jsonresult = {};
|
||||
const regexNum = /^[0-9]*$/; // test number
|
||||
for (item of items) {
|
||||
|
||||
let result = item.split(" ");
|
||||
if(result.length>=2 && regexNum.test(result[result.length-1]))
|
||||
{
|
||||
var wordstr="";
|
||||
for(var i=0;i<result.length-1;i++)
|
||||
wordstr=wordstr+result[i]+" ";
|
||||
|
||||
jsonresult[wordstr.trim()]= parseInt(result[result.length-1]);
|
||||
}
|
||||
}
|
||||
console.log("jsonresult="+JSON.stringify(jsonresult));
|
||||
return JSON.stringify(jsonresult);
|
||||
|
||||
}
|
||||
function getAsrMode(){
|
||||
|
||||
var item = null;
|
||||
var obj = document.getElementsByName("asr_mode");
|
||||
for (var i = 0; i < obj.length; i++) { //遍历Radio
|
||||
if (obj[i].checked) {
|
||||
item = obj[i].value;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if(isfilemode)
|
||||
{
|
||||
item= "offline";
|
||||
}
|
||||
console.log("asr mode"+item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function handleWithTimestamp(tmptext,tmptime)
|
||||
{
|
||||
console.log( "tmptext: " + tmptext);
|
||||
console.log( "tmptime: " + tmptime);
|
||||
if(tmptime==null || tmptime=="undefined" || tmptext.length<=0)
|
||||
{
|
||||
return tmptext;
|
||||
}
|
||||
tmptext=tmptext.replace(/。|?|,|、|\?|\.|\ /g, ","); // in case there are a lot of "。"
|
||||
var words=tmptext.split(","); // split to chinese sentence or english words
|
||||
var jsontime=JSON.parse(tmptime); //JSON.parse(tmptime.replace(/\]\]\[\[/g, "],[")); // in case there are a lot segments by VAD
|
||||
var char_index=0; // index for timestamp
|
||||
var text_withtime="";
|
||||
for(var i=0;i<words.length;i++)
|
||||
{
|
||||
if(words[i]=="undefined" || words[i].length<=0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
console.log("words===",words[i]);
|
||||
console.log( "words: " + words[i]+",time="+jsontime[char_index][0]/1000);
|
||||
if (/^[a-zA-Z]+$/.test(words[i]))
|
||||
{ // if it is english
|
||||
text_withtime=text_withtime+jsontime[char_index][0]/1000+":"+words[i]+"\n";
|
||||
char_index=char_index+1; //for english, timestamp unit is about a word
|
||||
}
|
||||
else{
|
||||
// if it is chinese
|
||||
text_withtime=text_withtime+jsontime[char_index][0]/1000+":"+words[i]+"\n";
|
||||
char_index=char_index+words[i].length; //for chinese, timestamp unit is about a char
|
||||
}
|
||||
}
|
||||
return text_withtime;
|
||||
|
||||
|
||||
}
|
||||
// 语音识别结果; 对jsonMsg数据解析,将识别结果附加到编辑框中
|
||||
function getJsonMessage( jsonMsg ) {
|
||||
//console.log(jsonMsg);
|
||||
console.log( "message: " + JSON.parse(jsonMsg.data)['text'] );
|
||||
var rectxt=""+JSON.parse(jsonMsg.data)['text'];
|
||||
var asrmodel=JSON.parse(jsonMsg.data)['mode'];
|
||||
var is_final=JSON.parse(jsonMsg.data)['is_final'];
|
||||
var timestamp=JSON.parse(jsonMsg.data)['timestamp'];
|
||||
if(asrmodel=="2pass-offline" || asrmodel=="offline")
|
||||
{
|
||||
|
||||
offline_text=offline_text+handleWithTimestamp(rectxt,timestamp); //rectxt; //.replace(/ +/g,"");
|
||||
rec_text=offline_text;
|
||||
}
|
||||
else
|
||||
{
|
||||
rec_text=rec_text+rectxt; //.replace(/ +/g,"");
|
||||
}
|
||||
var varArea=document.getElementById('varArea');
|
||||
|
||||
varArea.value=rec_text;
|
||||
console.log( "offline_text: " + asrmodel+","+offline_text);
|
||||
console.log( "rec_text: " + rec_text);
|
||||
if (isfilemode==true && is_final==false){
|
||||
console.log("call stop ws!");
|
||||
play_file();
|
||||
wsconnecter.wsStop();
|
||||
|
||||
info_div.innerHTML="请点击连接";
|
||||
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 连接状态响应
|
||||
function getConnState( connState ) {
|
||||
if ( connState === 0 ) { //on open
|
||||
|
||||
|
||||
info_div.innerHTML='连接成功!请点击开始';
|
||||
if (isfilemode==true){
|
||||
info_div.innerHTML='请耐心等待,大文件等待时间更长';
|
||||
start_file_send();
|
||||
}
|
||||
else
|
||||
{
|
||||
btnStart.disabled = false;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=true;
|
||||
}
|
||||
} else if ( connState === 1 ) {
|
||||
//stop();
|
||||
} else if ( connState === 2 ) {
|
||||
stop();
|
||||
console.log( 'connecttion error' );
|
||||
|
||||
alert("连接地址"+document.getElementById('wssip').value+"失败,请检查asr地址和端口。或试试界面上手动授权,再连接。");
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=false;
|
||||
|
||||
|
||||
info_div.innerHTML='请点击连接';
|
||||
}
|
||||
}
|
||||
|
||||
function record()
|
||||
{
|
||||
|
||||
rec.open( function(){
|
||||
rec.start();
|
||||
console.log("开始");
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = false;
|
||||
btnConnect.disabled=true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 识别启动、停止、清空操作
|
||||
function start() {
|
||||
|
||||
// 清除显示
|
||||
clear();
|
||||
//控件状态更新
|
||||
console.log("isfilemode"+isfilemode);
|
||||
|
||||
//启动连接
|
||||
var ret=wsconnecter.wsStart();
|
||||
// 1 is ok, 0 is error
|
||||
if(ret==1){
|
||||
info_div.innerHTML="正在连接asr服务器,请等待...";
|
||||
isRec = true;
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=true;
|
||||
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
info_div.innerHTML="请点击开始";
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function stop() {
|
||||
var chunk_size = new Array( 5, 10, 5 );
|
||||
var request = {
|
||||
"chunk_size": chunk_size,
|
||||
"wav_name": "h5",
|
||||
"is_speaking": false,
|
||||
"chunk_interval":10,
|
||||
"mode":getAsrMode(),
|
||||
};
|
||||
console.log(request);
|
||||
if(sampleBuf.length>0){
|
||||
wsconnecter.wsSend(sampleBuf);
|
||||
console.log("sampleBuf.length"+sampleBuf.length);
|
||||
sampleBuf=new Int16Array();
|
||||
}
|
||||
wsconnecter.wsSend( JSON.stringify(request) );
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 控件状态更新
|
||||
|
||||
isRec = false;
|
||||
info_div.innerHTML="发送完数据,请等候,正在识别...";
|
||||
|
||||
if(isfilemode==false){
|
||||
btnStop.disabled = true;
|
||||
btnStart.disabled = true;
|
||||
btnConnect.disabled=true;
|
||||
//wait 3s for asr result
|
||||
setTimeout(function(){
|
||||
console.log("call stop ws!");
|
||||
wsconnecter.wsStop();
|
||||
btnConnect.disabled=false;
|
||||
info_div.innerHTML="请点击连接";}, 3000 );
|
||||
|
||||
|
||||
|
||||
rec.stop(function(blob,duration){
|
||||
|
||||
console.log(blob);
|
||||
var audioBlob = Recorder.pcm2wav(data = {sampleRate:16000, bitRate:16, blob:blob},
|
||||
function(theblob,duration){
|
||||
console.log(theblob);
|
||||
var audio_record = document.getElementById('audio_record');
|
||||
audio_record.src = (window.URL||webkitURL).createObjectURL(theblob);
|
||||
audio_record.controls=true;
|
||||
//audio_record.play();
|
||||
|
||||
|
||||
} ,function(msg){
|
||||
console.log(msg);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
},function(errMsg){
|
||||
console.log("errMsg: " + errMsg);
|
||||
});
|
||||
}
|
||||
// 停止连接
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function clear() {
|
||||
|
||||
var varArea=document.getElementById('varArea');
|
||||
|
||||
varArea.value="";
|
||||
rec_text="";
|
||||
offline_text="";
|
||||
|
||||
}
|
||||
|
||||
|
||||
function recProcess( buffer, powerLevel, bufferDuration, bufferSampleRate,newBufferIdx,asyncEnd ) {
|
||||
if ( isRec === true ) {
|
||||
var data_48k = buffer[buffer.length-1];
|
||||
|
||||
var array_48k = new Array(data_48k);
|
||||
var data_16k=Recorder.SampleData(array_48k,bufferSampleRate,16000).data;
|
||||
|
||||
sampleBuf = Int16Array.from([...sampleBuf, ...data_16k]);
|
||||
var chunk_size=960; // for asr chunk_size [5, 10, 5]
|
||||
info_div.innerHTML=""+bufferDuration/1000+"s";
|
||||
while(sampleBuf.length>=chunk_size){
|
||||
sendBuf=sampleBuf.slice(0,chunk_size);
|
||||
sampleBuf=sampleBuf.slice(chunk_size,sampleBuf.length);
|
||||
wsconnecter.wsSend(sendBuf);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function getUseITN() {
|
||||
var obj = document.getElementsByName("use_itn");
|
||||
for (var i = 0; i < obj.length; i++) {
|
||||
if (obj[i].checked) {
|
||||
return obj[i].value === "true";
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
pcm编码器+编码引擎
|
||||
https://github.com/xiangyuecn/Recorder
|
||||
|
||||
编码原理:本编码器输出的pcm格式数据其实就是Recorder中的buffers原始数据(经过了重新采样),16位时为LE小端模式(Little Endian),并未经过任何编码处理
|
||||
|
||||
编码的代码和wav.js区别不大,pcm加上一个44字节wav头即成wav文件;所以要播放pcm就很简单了,直接转成wav文件来播放,已提供转换函数 Recorder.pcm2wav
|
||||
*/
|
||||
(function(){
|
||||
"use strict";
|
||||
|
||||
Recorder.prototype.enc_pcm={
|
||||
stable:true
|
||||
,testmsg:"pcm为未封装的原始音频数据,pcm数据文件无法直接播放;支持位数8位、16位(填在比特率里面),采样率取值无限制"
|
||||
};
|
||||
Recorder.prototype.pcm=function(res,True,False){
|
||||
var This=this,set=This.set
|
||||
,size=res.length
|
||||
,bitRate=set.bitRate==8?8:16;
|
||||
|
||||
var buffer=new ArrayBuffer(size*(bitRate/8));
|
||||
var data=new DataView(buffer);
|
||||
var offset=0;
|
||||
|
||||
// 写入采样数据
|
||||
if(bitRate==8) {
|
||||
for(var i=0;i<size;i++,offset++) {
|
||||
//16转8据说是雷霄骅的 https://blog.csdn.net/sevennight1989/article/details/85376149 细节比blqw的按比例的算法清晰点,虽然都有明显杂音
|
||||
var val=(res[i]>>8)+128;
|
||||
data.setInt8(offset,val,true);
|
||||
};
|
||||
}else{
|
||||
for (var i=0;i<size;i++,offset+=2){
|
||||
data.setInt16(offset,res[i],true);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
True(new Blob([data.buffer],{type:"audio/pcm"}));
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**pcm直接转码成wav,可以直接用来播放;需同时引入wav.js
|
||||
data: {
|
||||
sampleRate:16000 pcm的采样率
|
||||
bitRate:16 pcm的位数 取值:8 或 16
|
||||
blob:blob对象
|
||||
}
|
||||
data如果直接提供的blob将默认使用16位16khz的配置,仅用于测试
|
||||
True(wavBlob,duration)
|
||||
False(msg)
|
||||
**/
|
||||
Recorder.pcm2wav=function(data,True,False){
|
||||
if(data.slice && data.type!=null){//Blob 测试用
|
||||
data={blob:data};
|
||||
};
|
||||
var sampleRate=data.sampleRate||16000,bitRate=data.bitRate||16;
|
||||
if(!data.sampleRate || !data.bitRate){
|
||||
console.warn("pcm2wav必须提供sampleRate和bitRate");
|
||||
};
|
||||
if(!Recorder.prototype.wav){
|
||||
False("pcm2wav必须先加载wav编码器wav.js");
|
||||
return;
|
||||
};
|
||||
|
||||
var reader=new FileReader();
|
||||
reader.onloadend=function(){
|
||||
var pcm;
|
||||
if(bitRate==8){
|
||||
//8位转成16位
|
||||
var u8arr=new Uint8Array(reader.result);
|
||||
pcm=new Int16Array(u8arr.length);
|
||||
for(var j=0;j<u8arr.length;j++){
|
||||
pcm[j]=(u8arr[j]-128)<<8;
|
||||
};
|
||||
}else{
|
||||
pcm=new Int16Array(reader.result);
|
||||
};
|
||||
|
||||
Recorder({
|
||||
type:"wav"
|
||||
,sampleRate:sampleRate
|
||||
,bitRate:bitRate
|
||||
}).mock(pcm,sampleRate).stop(function(wavBlob,duration){
|
||||
True(wavBlob,duration);
|
||||
},False);
|
||||
};
|
||||
reader.readAsArrayBuffer(data.blob);
|
||||
};
|
||||
|
||||
|
||||
|
||||
})();
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
wav编码器+编码引擎
|
||||
https://github.com/xiangyuecn/Recorder
|
||||
|
||||
当然最佳推荐使用mp3、wav格式,代码也是优先照顾这两种格式
|
||||
浏览器支持情况
|
||||
https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
|
||||
|
||||
编码原理:给pcm数据加上一个44直接的wav头即成wav文件;pcm数据就是Recorder中的buffers原始数据(重新采样),16位时为LE小端模式(Little Endian),实质上是未经过任何编码处理
|
||||
*/
|
||||
(function(){
|
||||
"use strict";
|
||||
|
||||
Recorder.prototype.enc_wav={
|
||||
stable:true
|
||||
,testmsg:"支持位数8位、16位(填在比特率里面),采样率取值无限制"
|
||||
};
|
||||
Recorder.prototype.wav=function(res,True,False){
|
||||
var This=this,set=This.set
|
||||
,size=res.length
|
||||
,sampleRate=set.sampleRate
|
||||
,bitRate=set.bitRate==8?8:16;
|
||||
|
||||
//编码数据 https://github.com/mattdiamond/Recorderjs https://www.cnblogs.com/blqw/p/3782420.html https://www.cnblogs.com/xiaoqi/p/6993912.html
|
||||
var dataLength=size*(bitRate/8);
|
||||
var buffer=new ArrayBuffer(44+dataLength);
|
||||
var data=new DataView(buffer);
|
||||
|
||||
var offset=0;
|
||||
var writeString=function(str){
|
||||
for (var i=0;i<str.length;i++,offset++) {
|
||||
data.setUint8(offset,str.charCodeAt(i));
|
||||
};
|
||||
};
|
||||
var write16=function(v){
|
||||
data.setUint16(offset,v,true);
|
||||
offset+=2;
|
||||
};
|
||||
var write32=function(v){
|
||||
data.setUint32(offset,v,true);
|
||||
offset+=4;
|
||||
};
|
||||
|
||||
/* RIFF identifier */
|
||||
writeString('RIFF');
|
||||
/* RIFF chunk length */
|
||||
write32(36+dataLength);
|
||||
/* RIFF type */
|
||||
writeString('WAVE');
|
||||
/* format chunk identifier */
|
||||
writeString('fmt ');
|
||||
/* format chunk length */
|
||||
write32(16);
|
||||
/* sample format (raw) */
|
||||
write16(1);
|
||||
/* channel count */
|
||||
write16(1);
|
||||
/* sample rate */
|
||||
write32(sampleRate);
|
||||
/* byte rate (sample rate * block align) */
|
||||
write32(sampleRate*(bitRate/8));// *1 声道
|
||||
/* block align (channel count * bytes per sample) */
|
||||
write16(bitRate/8);// *1 声道
|
||||
/* bits per sample */
|
||||
write16(bitRate);
|
||||
/* data chunk identifier */
|
||||
writeString('data');
|
||||
/* data chunk length */
|
||||
write32(dataLength);
|
||||
// 写入采样数据
|
||||
if(bitRate==8) {
|
||||
for(var i=0;i<size;i++,offset++) {
|
||||
//16转8据说是雷霄骅的 https://blog.csdn.net/sevennight1989/article/details/85376149 细节比blqw的按比例的算法清晰点,虽然都有明显杂音
|
||||
var val=(res[i]>>8)+128;
|
||||
data.setInt8(offset,val,true);
|
||||
};
|
||||
}else{
|
||||
for (var i=0;i<size;i++,offset+=2){
|
||||
data.setInt16(offset,res[i],true);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
True(new Blob([data.buffer],{type:"audio/wav"}));
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2021-2023 by zhaoming,mali aihealthx.com */
|
||||
|
||||
function WebSocketConnectMethod( config ) { //定义socket连接方法类
|
||||
|
||||
|
||||
var speechSokt;
|
||||
var connKeeperID;
|
||||
|
||||
var msgHandle = config.msgHandle;
|
||||
var stateHandle = config.stateHandle;
|
||||
|
||||
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*|ws:\S*/))
|
||||
{
|
||||
console.log("Uri"+Uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("请检查wss地址正确性");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( 'WebSocket' in window ) {
|
||||
speechSokt = new WebSocket( Uri ); // 定义socket连接对象
|
||||
speechSokt.onopen = function(e){onOpen(e);}; // 定义响应函数
|
||||
speechSokt.onclose = function(e){
|
||||
console.log("onclose ws!");
|
||||
//speechSokt.close();
|
||||
onClose(e);
|
||||
};
|
||||
speechSokt.onmessage = function(e){onMessage(e);};
|
||||
speechSokt.onerror = function(e){onError(e);};
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
alert('当前浏览器不支持 WebSocket');
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// 定义停止与发送函数
|
||||
this.wsStop = function () {
|
||||
if(speechSokt != undefined) {
|
||||
console.log("stop ws!");
|
||||
speechSokt.close();
|
||||
}
|
||||
};
|
||||
|
||||
this.wsSend = function ( oneData ) {
|
||||
|
||||
if(speechSokt == undefined) return;
|
||||
if ( speechSokt.readyState === 1 ) { // 0:CONNECTING, 1:OPEN, 2:CLOSING, 3:CLOSED
|
||||
|
||||
speechSokt.send( oneData );
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// SOCEKT连接中的消息与状态响应
|
||||
function onOpen( e ) {
|
||||
// 发送json
|
||||
var chunk_size = new Array( 5, 10, 5 );
|
||||
var request = {
|
||||
"chunk_size": chunk_size,
|
||||
"wav_name": "h5",
|
||||
"is_speaking": true,
|
||||
"chunk_interval":10,
|
||||
"itn":getUseITN(),
|
||||
"mode":getAsrMode(),
|
||||
|
||||
};
|
||||
if(isfilemode)
|
||||
{
|
||||
request.wav_format=file_ext;
|
||||
if(file_ext=="wav")
|
||||
{
|
||||
request.wav_format="PCM";
|
||||
request.audio_fs=file_sample_rate;
|
||||
}
|
||||
}
|
||||
|
||||
var hotwords=getHotwords();
|
||||
|
||||
if(hotwords!=null )
|
||||
{
|
||||
request.hotwords=hotwords;
|
||||
}
|
||||
console.log(JSON.stringify(request));
|
||||
speechSokt.send(JSON.stringify(request));
|
||||
console.log("连接成功");
|
||||
stateHandle(0);
|
||||
|
||||
}
|
||||
|
||||
function onClose( e ) {
|
||||
stateHandle(1);
|
||||
}
|
||||
|
||||
function onMessage( e ) {
|
||||
|
||||
msgHandle( e );
|
||||
}
|
||||
|
||||
function onError( e ) {
|
||||
|
||||
info_div.innerHTML="连接"+e;
|
||||
console.log(e);
|
||||
stateHandle(2);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>语音识别</title>
|
||||
|
||||
</head>
|
||||
<body style="margin-left: 3%">
|
||||
<script src="recorder-core.js" charset="UTF-8"></script>
|
||||
<script src="wav.js" charset="UTF-8"></script>
|
||||
<script src="pcm.js" charset="UTF-8"></script>
|
||||
|
||||
|
||||
|
||||
<h1>FunASR Demo</h1>
|
||||
<h3>这里是FunASR开源项目体验demo,集成了VAD、ASR与标点等工业级别的模型,支持长音频离线文件转写,实时语音识别等,开源项目地址:https://github.com/alibaba-damo-academy/FunASR</h3>
|
||||
|
||||
<div class="div_class_topArea">
|
||||
|
||||
<div class="div_class_recordControl">
|
||||
asr服务器地址(必填):
|
||||
<br>
|
||||
<input id="wssip" type="text" onchange="addresschange()" style=" width: 100%;height:100%" value="wss://127.0.0.1:10095/"/>
|
||||
<br>
|
||||
<a id="wsslink" href="#" onclick="window.open('https://127.0.0.1:10095/', '_blank')"><div id="info_wslink">点此处手工授权wss://127.0.0.1:10095/</div></a>
|
||||
<br>
|
||||
<br>
|
||||
<div style="border:2px solid #ccc;">
|
||||
选择录音模式:<br/>
|
||||
|
||||
<label><input name="recoder_mode" onclick="on_recoder_mode_change()" type="radio" value="mic" checked="true"/>麦克风 </label>
|
||||
<label><input name="recoder_mode" onclick="on_recoder_mode_change()" type="radio" value="file" />文件 </label>
|
||||
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div id="mic_mode_div" style="border:2px solid #ccc;display:block;">
|
||||
选择asr模型模式:<br/>
|
||||
|
||||
<label><input name="asr_mode" type="radio" value="2pass" checked="true"/>2pass </label>
|
||||
<label><input name="asr_mode" type="radio" value="online" />online </label>
|
||||
<label><input name="asr_mode" type="radio" value="offline" />offline </label>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="rec_mode_div" style="border:2px solid #ccc;display:none;">
|
||||
|
||||
|
||||
<input type="file" id="upfile">
|
||||
|
||||
</div>
|
||||
<br>
|
||||
<div id="use_itn_div" style="border:2px solid #ccc;display:block;">
|
||||
逆文本标准化(ITN):<br/>
|
||||
<label><input name="use_itn" type="radio" value="false" checked="true"/>否 </label>
|
||||
<label><input name="use_itn" type="radio" value="true" />是 </label>
|
||||
</div>
|
||||
<br>
|
||||
<div style="border:2px solid #ccc;">
|
||||
热词设置(一行一个关键字,空格隔开权重,如"阿里巴巴 20"):
|
||||
<br>
|
||||
|
||||
|
||||
<textarea rows="3" id="varHot" style=" width: 100%;height:100%" >阿里巴巴 20 hello world 40</textarea>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
语音识别结果显示:
|
||||
<br>
|
||||
|
||||
<textarea rows="10" id="varArea" readonly="true" style=" width: 100%;height:100%" ></textarea>
|
||||
<br>
|
||||
<div id="info_div">请点击开始</div>
|
||||
<div class="div_class_buttons">
|
||||
<button id="btnConnect">连接</button>
|
||||
<button id="btnStart">开始</button>
|
||||
<button id="btnStop">停止</button>
|
||||
|
||||
</div>
|
||||
|
||||
<audio id="audio_record" type="audio/wav" controls style="margin-top: 12px; width: 100%;"></audio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="wsconnecter.js" charset="utf-8"></script>
|
||||
<script src="main.js" charset="utf-8"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,583 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2022-2023 by zhaoming,mali aihealthx.com */
|
||||
|
||||
|
||||
// 连接; 定义socket连接类对象与语音对象
|
||||
var wsconnecter = new WebSocketConnectMethod({msgHandle:getJsonMessage,stateHandle:getConnState});
|
||||
var audioBlob;
|
||||
|
||||
// 录音; 定义录音对象,wav格式
|
||||
var rec = Recorder({
|
||||
type:"pcm",
|
||||
bitRate:16,
|
||||
sampleRate:16000,
|
||||
onProcess:recProcess
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
var sampleBuf=new Int16Array();
|
||||
// 定义按钮响应事件
|
||||
var btnStart = document.getElementById('btnStart');
|
||||
btnStart.onclick = record;
|
||||
var btnStop = document.getElementById('btnStop');
|
||||
btnStop.onclick = stop;
|
||||
btnStop.disabled = true;
|
||||
btnStart.disabled = true;
|
||||
|
||||
btnConnect= document.getElementById('btnConnect');
|
||||
btnConnect.onclick = start;
|
||||
|
||||
var awsslink= document.getElementById('wsslink');
|
||||
|
||||
|
||||
var rec_text=""; // for online rec asr result
|
||||
var offline_text=""; // for offline rec asr result
|
||||
var info_div = document.getElementById('info_div');
|
||||
|
||||
var upfile = document.getElementById('upfile');
|
||||
|
||||
|
||||
|
||||
var isfilemode=false; // if it is in file mode
|
||||
var file_ext="";
|
||||
var file_sample_rate=16000; //for wav file sample rate
|
||||
var file_data_array; // array to save file data
|
||||
|
||||
var totalsend=0;
|
||||
|
||||
|
||||
var now_ipaddress=window.location.href;
|
||||
now_ipaddress=now_ipaddress.replace("https://","wss://");
|
||||
now_ipaddress=now_ipaddress.replace("static/online/index.html","");
|
||||
var localport=window.location.port;
|
||||
if (localport == '')
|
||||
{
|
||||
now_ipaddress=now_ipaddress.substring(0, now_ipaddress.length - 1) + ':10096' + now_ipaddress.substring(now_ipaddress.length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
now_ipaddress=now_ipaddress.replace(localport,"10096");
|
||||
}
|
||||
document.getElementById('wssip').value=now_ipaddress;
|
||||
addresschange();
|
||||
function addresschange()
|
||||
{
|
||||
|
||||
var Uri = document.getElementById('wssip').value;
|
||||
document.getElementById('info_wslink').innerHTML="点此处手工授权(IOS手机)";
|
||||
Uri=Uri.replace(/wss/g,"https");
|
||||
console.log("addresschange uri=",Uri);
|
||||
|
||||
awsslink.onclick=function(){
|
||||
window.open(Uri, '_blank');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
upfile.onclick=function()
|
||||
{
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=false;
|
||||
|
||||
}
|
||||
|
||||
// from https://github.com/xiangyuecn/Recorder/tree/master
|
||||
var readWavInfo=function(bytes){
|
||||
//读取wav文件头,统一成44字节的头
|
||||
if(bytes.byteLength<44){
|
||||
return null;
|
||||
};
|
||||
var wavView=bytes;
|
||||
var eq=function(p,s){
|
||||
for(var i=0;i<s.length;i++){
|
||||
if(wavView[p+i]!=s.charCodeAt(i)){
|
||||
return false;
|
||||
};
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
if(eq(0,"RIFF")&&eq(8,"WAVEfmt ")){
|
||||
|
||||
var numCh=wavView[22];
|
||||
if(wavView[20]==1 && (numCh==1||numCh==2)){//raw pcm 单或双声道
|
||||
var sampleRate=wavView[24]+(wavView[25]<<8)+(wavView[26]<<16)+(wavView[27]<<24);
|
||||
var bitRate=wavView[34]+(wavView[35]<<8);
|
||||
var heads=[wavView.subarray(0,12)],headSize=12;//head只保留必要的块
|
||||
//搜索data块的位置
|
||||
var dataPos=0; // 44 或有更多块
|
||||
for(var i=12,iL=wavView.length-8;i<iL;){
|
||||
if(wavView[i]==100&&wavView[i+1]==97&&wavView[i+2]==116&&wavView[i+3]==97){//eq(i,"data")
|
||||
heads.push(wavView.subarray(i,i+8));
|
||||
headSize+=8;
|
||||
dataPos=i+8;break;
|
||||
}
|
||||
var i0=i;
|
||||
i+=4;
|
||||
i+=4+wavView[i]+(wavView[i+1]<<8)+(wavView[i+2]<<16)+(wavView[i+3]<<24);
|
||||
if(i0==12){//fmt
|
||||
heads.push(wavView.subarray(i0,i));
|
||||
headSize+=i-i0;
|
||||
}
|
||||
}
|
||||
if(dataPos){
|
||||
var wavHead=new Uint8Array(headSize);
|
||||
for(var i=0,n=0;i<heads.length;i++){
|
||||
wavHead.set(heads[i],n);n+=heads[i].length;
|
||||
}
|
||||
return {
|
||||
sampleRate:sampleRate
|
||||
,bitRate:bitRate
|
||||
,numChannels:numCh
|
||||
,wavHead44:wavHead
|
||||
,dataPos:dataPos
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return null;
|
||||
};
|
||||
|
||||
upfile.onchange = function () {
|
||||
var len = this.files.length;
|
||||
for(let i = 0; i < len; i++) {
|
||||
|
||||
let fileAudio = new FileReader();
|
||||
fileAudio.readAsArrayBuffer(this.files[i]);
|
||||
|
||||
file_ext=this.files[i].name.split('.').pop().toLowerCase();
|
||||
var audioblob;
|
||||
fileAudio.onload = function() {
|
||||
audioblob = fileAudio.result;
|
||||
|
||||
|
||||
file_data_array=audioblob;
|
||||
|
||||
|
||||
info_div.innerHTML='请点击连接进行识别';
|
||||
|
||||
}
|
||||
|
||||
fileAudio.onerror = function(e) {
|
||||
console.log('error' + e);
|
||||
}
|
||||
}
|
||||
// for wav file, we get the sample rate
|
||||
if(file_ext=="wav")
|
||||
for(let i = 0; i < len; i++) {
|
||||
|
||||
let fileAudio = new FileReader();
|
||||
fileAudio.readAsArrayBuffer(this.files[i]);
|
||||
fileAudio.onload = function() {
|
||||
audioblob = new Uint8Array(fileAudio.result);
|
||||
|
||||
// for wav file, we can get the sample rate
|
||||
var info=readWavInfo(audioblob);
|
||||
console.log(info);
|
||||
file_sample_rate=info.sampleRate;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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(); //not auto play
|
||||
}
|
||||
function start_file_send()
|
||||
{
|
||||
sampleBuf=new Uint8Array( 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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
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';
|
||||
|
||||
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=false;
|
||||
isfilemode=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById("mic_mode_div").style.display = 'none';
|
||||
document.getElementById("rec_mode_div").style.display = 'block';
|
||||
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=true;
|
||||
isfilemode=true;
|
||||
info_div.innerHTML='请点击选择文件';
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getHotwords(){
|
||||
|
||||
var obj = document.getElementById("varHot");
|
||||
|
||||
if(typeof(obj) == 'undefined' || obj==null || obj.value.length<=0){
|
||||
return null;
|
||||
}
|
||||
let val = obj.value.toString();
|
||||
|
||||
console.log("hotwords="+val);
|
||||
let items = val.split(/[(\r\n)\r\n]+/); //split by \r\n
|
||||
var jsonresult = {};
|
||||
const regexNum = /^[0-9]*$/; // test number
|
||||
for (item of items) {
|
||||
|
||||
let result = item.split(" ");
|
||||
if(result.length>=2 && regexNum.test(result[result.length-1]))
|
||||
{
|
||||
var wordstr="";
|
||||
for(var i=0;i<result.length-1;i++)
|
||||
wordstr=wordstr+result[i]+" ";
|
||||
|
||||
jsonresult[wordstr.trim()]= parseInt(result[result.length-1]);
|
||||
}
|
||||
}
|
||||
console.log("jsonresult="+JSON.stringify(jsonresult));
|
||||
return JSON.stringify(jsonresult);
|
||||
|
||||
}
|
||||
function getAsrMode(){
|
||||
|
||||
var item = null;
|
||||
var obj = document.getElementsByName("asr_mode");
|
||||
for (var i = 0; i < obj.length; i++) { //遍历Radio
|
||||
if (obj[i].checked) {
|
||||
item = obj[i].value;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if(isfilemode)
|
||||
{
|
||||
item= "offline";
|
||||
}
|
||||
console.log("asr mode"+item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function handleWithTimestamp(tmptext,tmptime)
|
||||
{
|
||||
console.log( "tmptext: " + tmptext);
|
||||
console.log( "tmptime: " + tmptime);
|
||||
if(tmptime==null || tmptime=="undefined" || tmptext.length<=0)
|
||||
{
|
||||
return tmptext;
|
||||
}
|
||||
tmptext=tmptext.replace(/。|?|,|、|\?|\.|\ /g, ","); // in case there are a lot of "。"
|
||||
var words=tmptext.split(","); // split to chinese sentence or english words
|
||||
var jsontime=JSON.parse(tmptime); //JSON.parse(tmptime.replace(/\]\]\[\[/g, "],[")); // in case there are a lot segments by VAD
|
||||
var char_index=0; // index for timestamp
|
||||
var text_withtime="";
|
||||
for(var i=0;i<words.length;i++)
|
||||
{
|
||||
if(words[i]=="undefined" || words[i].length<=0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
console.log("words===",words[i]);
|
||||
console.log( "words: " + words[i]+",time="+jsontime[char_index][0]/1000);
|
||||
if (/^[a-zA-Z]+$/.test(words[i]))
|
||||
{ // if it is english
|
||||
text_withtime=text_withtime+jsontime[char_index][0]/1000+":"+words[i]+"\n";
|
||||
char_index=char_index+1; //for english, timestamp unit is about a word
|
||||
}
|
||||
else{
|
||||
// if it is chinese
|
||||
text_withtime=text_withtime+jsontime[char_index][0]/1000+":"+words[i]+"\n";
|
||||
char_index=char_index+words[i].length; //for chinese, timestamp unit is about a char
|
||||
}
|
||||
}
|
||||
return text_withtime;
|
||||
|
||||
|
||||
}
|
||||
// 语音识别结果; 对jsonMsg数据解析,将识别结果附加到编辑框中
|
||||
function getJsonMessage( jsonMsg ) {
|
||||
//console.log(jsonMsg);
|
||||
console.log( "message: " + JSON.parse(jsonMsg.data)['text'] );
|
||||
var rectxt=""+JSON.parse(jsonMsg.data)['text'];
|
||||
var asrmodel=JSON.parse(jsonMsg.data)['mode'];
|
||||
var is_final=JSON.parse(jsonMsg.data)['is_final'];
|
||||
var timestamp=JSON.parse(jsonMsg.data)['timestamp'];
|
||||
if(asrmodel=="2pass-offline" || asrmodel=="offline")
|
||||
{
|
||||
|
||||
offline_text=offline_text+handleWithTimestamp(rectxt,timestamp); //rectxt; //.replace(/ +/g,"");
|
||||
rec_text=offline_text;
|
||||
}
|
||||
else
|
||||
{
|
||||
rec_text=rec_text+rectxt; //.replace(/ +/g,"");
|
||||
}
|
||||
var varArea=document.getElementById('varArea');
|
||||
|
||||
varArea.value=rec_text;
|
||||
console.log( "offline_text: " + asrmodel+","+offline_text);
|
||||
console.log( "rec_text: " + rec_text);
|
||||
if (isfilemode==true && is_final==false){
|
||||
console.log("call stop ws!");
|
||||
play_file();
|
||||
wsconnecter.wsStop();
|
||||
|
||||
info_div.innerHTML="请点击连接";
|
||||
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 连接状态响应
|
||||
function getConnState( connState ) {
|
||||
if ( connState === 0 ) { //on open
|
||||
|
||||
|
||||
info_div.innerHTML='连接成功!请点击开始';
|
||||
if (isfilemode==true){
|
||||
info_div.innerHTML='请耐心等待,大文件等待时间更长';
|
||||
start_file_send();
|
||||
}
|
||||
else
|
||||
{
|
||||
btnStart.disabled = false;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=true;
|
||||
}
|
||||
} else if ( connState === 1 ) {
|
||||
//stop();
|
||||
} else if ( connState === 2 ) {
|
||||
stop();
|
||||
console.log( 'connecttion error' );
|
||||
|
||||
alert("连接地址"+document.getElementById('wssip').value+"失败,请检查asr地址和端口。或试试界面上手动授权,再连接。");
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=false;
|
||||
|
||||
|
||||
info_div.innerHTML='请点击连接';
|
||||
}
|
||||
}
|
||||
|
||||
function record()
|
||||
{
|
||||
|
||||
rec.open( function(){
|
||||
rec.start();
|
||||
console.log("开始");
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = false;
|
||||
btnConnect.disabled=true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 识别启动、停止、清空操作
|
||||
function start() {
|
||||
|
||||
// 清除显示
|
||||
clear();
|
||||
//控件状态更新
|
||||
console.log("isfilemode"+isfilemode);
|
||||
|
||||
//启动连接
|
||||
var ret=wsconnecter.wsStart();
|
||||
// 1 is ok, 0 is error
|
||||
if(ret==1){
|
||||
info_div.innerHTML="正在连接asr服务器,请等待...";
|
||||
isRec = true;
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=true;
|
||||
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
info_div.innerHTML="请点击开始";
|
||||
btnStart.disabled = true;
|
||||
btnStop.disabled = true;
|
||||
btnConnect.disabled=false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function stop() {
|
||||
var chunk_size = new Array( 5, 10, 5 );
|
||||
var request = {
|
||||
"chunk_size": chunk_size,
|
||||
"wav_name": "h5",
|
||||
"is_speaking": false,
|
||||
"chunk_interval":10,
|
||||
"mode":getAsrMode(),
|
||||
};
|
||||
console.log(request);
|
||||
if(sampleBuf.length>0){
|
||||
wsconnecter.wsSend(sampleBuf);
|
||||
console.log("sampleBuf.length"+sampleBuf.length);
|
||||
sampleBuf=new Int16Array();
|
||||
}
|
||||
wsconnecter.wsSend( JSON.stringify(request) );
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 控件状态更新
|
||||
|
||||
isRec = false;
|
||||
info_div.innerHTML="发送完数据,请等候,正在识别...";
|
||||
|
||||
if(isfilemode==false){
|
||||
btnStop.disabled = true;
|
||||
btnStart.disabled = true;
|
||||
btnConnect.disabled=true;
|
||||
//wait 3s for asr result
|
||||
setTimeout(function(){
|
||||
console.log("call stop ws!");
|
||||
wsconnecter.wsStop();
|
||||
btnConnect.disabled=false;
|
||||
info_div.innerHTML="请点击连接";}, 3000 );
|
||||
|
||||
|
||||
|
||||
rec.stop(function(blob,duration){
|
||||
|
||||
console.log(blob);
|
||||
var audioBlob = Recorder.pcm2wav(data = {sampleRate:16000, bitRate:16, blob:blob},
|
||||
function(theblob,duration){
|
||||
console.log(theblob);
|
||||
var audio_record = document.getElementById('audio_record');
|
||||
audio_record.src = (window.URL||webkitURL).createObjectURL(theblob);
|
||||
audio_record.controls=true;
|
||||
//audio_record.play();
|
||||
|
||||
|
||||
} ,function(msg){
|
||||
console.log(msg);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
},function(errMsg){
|
||||
console.log("errMsg: " + errMsg);
|
||||
});
|
||||
}
|
||||
// 停止连接
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function clear() {
|
||||
|
||||
var varArea=document.getElementById('varArea');
|
||||
|
||||
varArea.value="";
|
||||
rec_text="";
|
||||
offline_text="";
|
||||
|
||||
}
|
||||
|
||||
|
||||
function recProcess( buffer, powerLevel, bufferDuration, bufferSampleRate,newBufferIdx,asyncEnd ) {
|
||||
if ( isRec === true ) {
|
||||
var data_48k = buffer[buffer.length-1];
|
||||
|
||||
var array_48k = new Array(data_48k);
|
||||
var data_16k=Recorder.SampleData(array_48k,bufferSampleRate,16000).data;
|
||||
|
||||
sampleBuf = Int16Array.from([...sampleBuf, ...data_16k]);
|
||||
var chunk_size=960; // for asr chunk_size [5, 10, 5]
|
||||
info_div.innerHTML=""+bufferDuration/1000+"s";
|
||||
while(sampleBuf.length>=chunk_size){
|
||||
sendBuf=sampleBuf.slice(0,chunk_size);
|
||||
sampleBuf=sampleBuf.slice(chunk_size,sampleBuf.length);
|
||||
wsconnecter.wsSend(sendBuf);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function getUseITN() {
|
||||
var obj = document.getElementsByName("use_itn");
|
||||
for (var i = 0; i < obj.length; i++) {
|
||||
if (obj[i].checked) {
|
||||
return obj[i].value === "true";
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
pcm编码器+编码引擎
|
||||
https://github.com/xiangyuecn/Recorder
|
||||
|
||||
编码原理:本编码器输出的pcm格式数据其实就是Recorder中的buffers原始数据(经过了重新采样),16位时为LE小端模式(Little Endian),并未经过任何编码处理
|
||||
|
||||
编码的代码和wav.js区别不大,pcm加上一个44字节wav头即成wav文件;所以要播放pcm就很简单了,直接转成wav文件来播放,已提供转换函数 Recorder.pcm2wav
|
||||
*/
|
||||
(function(){
|
||||
"use strict";
|
||||
|
||||
Recorder.prototype.enc_pcm={
|
||||
stable:true
|
||||
,testmsg:"pcm为未封装的原始音频数据,pcm数据文件无法直接播放;支持位数8位、16位(填在比特率里面),采样率取值无限制"
|
||||
};
|
||||
Recorder.prototype.pcm=function(res,True,False){
|
||||
var This=this,set=This.set
|
||||
,size=res.length
|
||||
,bitRate=set.bitRate==8?8:16;
|
||||
|
||||
var buffer=new ArrayBuffer(size*(bitRate/8));
|
||||
var data=new DataView(buffer);
|
||||
var offset=0;
|
||||
|
||||
// 写入采样数据
|
||||
if(bitRate==8) {
|
||||
for(var i=0;i<size;i++,offset++) {
|
||||
//16转8据说是雷霄骅的 https://blog.csdn.net/sevennight1989/article/details/85376149 细节比blqw的按比例的算法清晰点,虽然都有明显杂音
|
||||
var val=(res[i]>>8)+128;
|
||||
data.setInt8(offset,val,true);
|
||||
};
|
||||
}else{
|
||||
for (var i=0;i<size;i++,offset+=2){
|
||||
data.setInt16(offset,res[i],true);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
True(new Blob([data.buffer],{type:"audio/pcm"}));
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**pcm直接转码成wav,可以直接用来播放;需同时引入wav.js
|
||||
data: {
|
||||
sampleRate:16000 pcm的采样率
|
||||
bitRate:16 pcm的位数 取值:8 或 16
|
||||
blob:blob对象
|
||||
}
|
||||
data如果直接提供的blob将默认使用16位16khz的配置,仅用于测试
|
||||
True(wavBlob,duration)
|
||||
False(msg)
|
||||
**/
|
||||
Recorder.pcm2wav=function(data,True,False){
|
||||
if(data.slice && data.type!=null){//Blob 测试用
|
||||
data={blob:data};
|
||||
};
|
||||
var sampleRate=data.sampleRate||16000,bitRate=data.bitRate||16;
|
||||
if(!data.sampleRate || !data.bitRate){
|
||||
console.warn("pcm2wav必须提供sampleRate和bitRate");
|
||||
};
|
||||
if(!Recorder.prototype.wav){
|
||||
False("pcm2wav必须先加载wav编码器wav.js");
|
||||
return;
|
||||
};
|
||||
|
||||
var reader=new FileReader();
|
||||
reader.onloadend=function(){
|
||||
var pcm;
|
||||
if(bitRate==8){
|
||||
//8位转成16位
|
||||
var u8arr=new Uint8Array(reader.result);
|
||||
pcm=new Int16Array(u8arr.length);
|
||||
for(var j=0;j<u8arr.length;j++){
|
||||
pcm[j]=(u8arr[j]-128)<<8;
|
||||
};
|
||||
}else{
|
||||
pcm=new Int16Array(reader.result);
|
||||
};
|
||||
|
||||
Recorder({
|
||||
type:"wav"
|
||||
,sampleRate:sampleRate
|
||||
,bitRate:bitRate
|
||||
}).mock(pcm,sampleRate).stop(function(wavBlob,duration){
|
||||
True(wavBlob,duration);
|
||||
},False);
|
||||
};
|
||||
reader.readAsArrayBuffer(data.blob);
|
||||
};
|
||||
|
||||
|
||||
|
||||
})();
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
wav编码器+编码引擎
|
||||
https://github.com/xiangyuecn/Recorder
|
||||
|
||||
当然最佳推荐使用mp3、wav格式,代码也是优先照顾这两种格式
|
||||
浏览器支持情况
|
||||
https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
|
||||
|
||||
编码原理:给pcm数据加上一个44直接的wav头即成wav文件;pcm数据就是Recorder中的buffers原始数据(重新采样),16位时为LE小端模式(Little Endian),实质上是未经过任何编码处理
|
||||
*/
|
||||
(function(){
|
||||
"use strict";
|
||||
|
||||
Recorder.prototype.enc_wav={
|
||||
stable:true
|
||||
,testmsg:"支持位数8位、16位(填在比特率里面),采样率取值无限制"
|
||||
};
|
||||
Recorder.prototype.wav=function(res,True,False){
|
||||
var This=this,set=This.set
|
||||
,size=res.length
|
||||
,sampleRate=set.sampleRate
|
||||
,bitRate=set.bitRate==8?8:16;
|
||||
|
||||
//编码数据 https://github.com/mattdiamond/Recorderjs https://www.cnblogs.com/blqw/p/3782420.html https://www.cnblogs.com/xiaoqi/p/6993912.html
|
||||
var dataLength=size*(bitRate/8);
|
||||
var buffer=new ArrayBuffer(44+dataLength);
|
||||
var data=new DataView(buffer);
|
||||
|
||||
var offset=0;
|
||||
var writeString=function(str){
|
||||
for (var i=0;i<str.length;i++,offset++) {
|
||||
data.setUint8(offset,str.charCodeAt(i));
|
||||
};
|
||||
};
|
||||
var write16=function(v){
|
||||
data.setUint16(offset,v,true);
|
||||
offset+=2;
|
||||
};
|
||||
var write32=function(v){
|
||||
data.setUint32(offset,v,true);
|
||||
offset+=4;
|
||||
};
|
||||
|
||||
/* RIFF identifier */
|
||||
writeString('RIFF');
|
||||
/* RIFF chunk length */
|
||||
write32(36+dataLength);
|
||||
/* RIFF type */
|
||||
writeString('WAVE');
|
||||
/* format chunk identifier */
|
||||
writeString('fmt ');
|
||||
/* format chunk length */
|
||||
write32(16);
|
||||
/* sample format (raw) */
|
||||
write16(1);
|
||||
/* channel count */
|
||||
write16(1);
|
||||
/* sample rate */
|
||||
write32(sampleRate);
|
||||
/* byte rate (sample rate * block align) */
|
||||
write32(sampleRate*(bitRate/8));// *1 声道
|
||||
/* block align (channel count * bytes per sample) */
|
||||
write16(bitRate/8);// *1 声道
|
||||
/* bits per sample */
|
||||
write16(bitRate);
|
||||
/* data chunk identifier */
|
||||
writeString('data');
|
||||
/* data chunk length */
|
||||
write32(dataLength);
|
||||
// 写入采样数据
|
||||
if(bitRate==8) {
|
||||
for(var i=0;i<size;i++,offset++) {
|
||||
//16转8据说是雷霄骅的 https://blog.csdn.net/sevennight1989/article/details/85376149 细节比blqw的按比例的算法清晰点,虽然都有明显杂音
|
||||
var val=(res[i]>>8)+128;
|
||||
data.setInt8(offset,val,true);
|
||||
};
|
||||
}else{
|
||||
for (var i=0;i<size;i++,offset+=2){
|
||||
data.setInt16(offset,res[i],true);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
True(new Blob([data.buffer],{type:"audio/wav"}));
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
/* 2021-2023 by zhaoming,mali aihealthx.com */
|
||||
|
||||
function WebSocketConnectMethod( config ) { //定义socket连接方法类
|
||||
|
||||
|
||||
var speechSokt;
|
||||
var connKeeperID;
|
||||
|
||||
var msgHandle = config.msgHandle;
|
||||
var stateHandle = config.stateHandle;
|
||||
|
||||
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*|ws:\S*/))
|
||||
{
|
||||
console.log("Uri"+Uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("请检查wss地址正确性");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( 'WebSocket' in window ) {
|
||||
speechSokt = new WebSocket( Uri ); // 定义socket连接对象
|
||||
speechSokt.onopen = function(e){onOpen(e);}; // 定义响应函数
|
||||
speechSokt.onclose = function(e){
|
||||
console.log("onclose ws!");
|
||||
//speechSokt.close();
|
||||
onClose(e);
|
||||
};
|
||||
speechSokt.onmessage = function(e){onMessage(e);};
|
||||
speechSokt.onerror = function(e){onError(e);};
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
alert('当前浏览器不支持 WebSocket');
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// 定义停止与发送函数
|
||||
this.wsStop = function () {
|
||||
if(speechSokt != undefined) {
|
||||
console.log("stop ws!");
|
||||
speechSokt.close();
|
||||
}
|
||||
};
|
||||
|
||||
this.wsSend = function ( oneData ) {
|
||||
|
||||
if(speechSokt == undefined) return;
|
||||
if ( speechSokt.readyState === 1 ) { // 0:CONNECTING, 1:OPEN, 2:CLOSING, 3:CLOSED
|
||||
|
||||
speechSokt.send( oneData );
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// SOCEKT连接中的消息与状态响应
|
||||
function onOpen( e ) {
|
||||
// 发送json
|
||||
var chunk_size = new Array( 5, 10, 5 );
|
||||
var request = {
|
||||
"chunk_size": chunk_size,
|
||||
"wav_name": "h5",
|
||||
"is_speaking": true,
|
||||
"chunk_interval":10,
|
||||
"itn":getUseITN(),
|
||||
"mode":getAsrMode(),
|
||||
|
||||
};
|
||||
if(isfilemode)
|
||||
{
|
||||
request.wav_format=file_ext;
|
||||
if(file_ext=="wav")
|
||||
{
|
||||
request.wav_format="PCM";
|
||||
request.audio_fs=file_sample_rate;
|
||||
}
|
||||
}
|
||||
|
||||
var hotwords=getHotwords();
|
||||
|
||||
if(hotwords!=null )
|
||||
{
|
||||
request.hotwords=hotwords;
|
||||
}
|
||||
console.log(JSON.stringify(request));
|
||||
speechSokt.send(JSON.stringify(request));
|
||||
console.log("连接成功");
|
||||
stateHandle(0);
|
||||
|
||||
}
|
||||
|
||||
function onClose( e ) {
|
||||
stateHandle(1);
|
||||
}
|
||||
|
||||
function onMessage( e ) {
|
||||
|
||||
msgHandle( e );
|
||||
}
|
||||
|
||||
function onError( e ) {
|
||||
|
||||
info_div.innerHTML="连接"+e;
|
||||
console.log(e);
|
||||
stateHandle(2);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user