276 lines
7.7 KiB
JavaScript
276 lines
7.7 KiB
JavaScript
|
/**
|
|||
|
* 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;
|
|||
|
var isfilemode = true; // if it is in file mode
|
|||
|
// 录音; 定义录音对象,wav格式
|
|||
|
var rec = Recorder({
|
|||
|
type: "pcm",
|
|||
|
bitRate: 16,
|
|||
|
sampleRate: 16000,
|
|||
|
onProcess: recProcess
|
|||
|
});
|
|||
|
|
|||
|
var sampleBuf = new Int16Array();
|
|||
|
|
|||
|
var rec_text = ""; // for online rec asr result
|
|||
|
var offline_text = ""; // for offline rec asr result
|
|||
|
|
|||
|
|
|||
|
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;
|
|||
|
|
|||
|
addresschange();
|
|||
|
function addresschange() {
|
|||
|
var Uri = 'ws://192.168.0.232:10095';
|
|||
|
Uri = Uri.replace(/wss/g, "https");
|
|||
|
window.open(Uri, '_blank');
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
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;
|
|||
|
};
|
|||
|
|
|||
|
function upfileOnchange(files) {
|
|||
|
this.files = [files];
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
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);
|
|||
|
|
|||
|
var info = readWavInfo(audioblob);
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
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 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(),
|
|||
|
};
|
|||
|
if (sampleBuf.length > 0) {
|
|||
|
wsconnecter.wsSend(sampleBuf);
|
|||
|
sampleBuf = new Int16Array();
|
|||
|
}
|
|||
|
wsconnecter.wsSend(JSON.stringify(request));
|
|||
|
// 控件状态更新
|
|||
|
isRec = false;
|
|||
|
if (isfilemode == false) {
|
|||
|
//wait 3s for asr result
|
|||
|
setTimeout(function () {
|
|||
|
wsconnecter.wsStop();
|
|||
|
}, 3000);
|
|||
|
rec.stop(function (blob, duration) {
|
|||
|
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;
|
|||
|
}, function (msg) {
|
|||
|
console.log(msg);
|
|||
|
}
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
}, function (errMsg) {
|
|||
|
console.log("errMsg: " + errMsg);
|
|||
|
});
|
|||
|
}
|
|||
|
// 停止连接
|
|||
|
}
|
|||
|
|
|||
|
function getAsrMode() {
|
|||
|
return 'offline';
|
|||
|
}
|
|||
|
function getHotwords() {
|
|||
|
return null
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function handleWithTimestamp(tmptext, 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;
|
|||
|
}
|
|||
|
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 {
|
|||
|
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) {
|
|||
|
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;
|
|||
|
}
|
|||
|
videoText = rec_text;
|
|||
|
if (is_final == true) {
|
|||
|
play_file();
|
|||
|
wsconnecter.wsStop();
|
|||
|
btnConnect.disabled = false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 连接状态响应
|
|||
|
function getConnState(connState) {
|
|||
|
if (connState === 0) start_file_send();
|
|||
|
}
|
|||
|
|
|||
|
// 识别启动、停止、清空操作
|
|||
|
function start() {
|
|||
|
var ret = wsconnecter.wsStart();//启动连接
|
|||
|
return ret == 1 ? 1 : 0;
|
|||
|
}
|
|||
|
|
|||
|
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]
|
|||
|
while (sampleBuf.length >= chunk_size) {
|
|||
|
sendBuf = sampleBuf.slice(0, chunk_size);
|
|||
|
sampleBuf = sampleBuf.slice(chunk_size, sampleBuf.length);
|
|||
|
wsconnecter.wsSend(sendBuf);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function getUseITN() {
|
|||
|
return false;
|
|||
|
}
|