mirror of
https://github.com/RYDE-WORK/Langchain-Chatchat.git
synced 2026-01-29 10:13:20 +08:00
merge dev_fastchat
This commit is contained in:
parent
18fe1b97f9
commit
90eb45ac46
@ -1,243 +0,0 @@
|
||||
/*
|
||||
* base64.js
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License.
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* References:
|
||||
* http://en.wikipedia.org/wiki/Base64
|
||||
*/
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
? module.exports = factory(global)
|
||||
: typeof define === 'function' && define.amd
|
||||
? define(factory) : factory(global)
|
||||
}((
|
||||
typeof self !== 'undefined' ? self
|
||||
: typeof window !== 'undefined' ? window
|
||||
: typeof global !== 'undefined' ? global
|
||||
: this
|
||||
), function(global) {
|
||||
'use strict';
|
||||
// existing version for noConflict()
|
||||
global = global || {};
|
||||
var _Base64 = global.Base64;
|
||||
var version = "2.5.1";
|
||||
// if node.js and NOT React Native, we use Buffer
|
||||
var buffer;
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
try {
|
||||
buffer = eval("require('buffer').Buffer");
|
||||
} catch (err) {
|
||||
buffer = undefined;
|
||||
}
|
||||
}
|
||||
// constants
|
||||
var b64chars
|
||||
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
var b64tab = function(bin) {
|
||||
var t = {};
|
||||
for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
|
||||
return t;
|
||||
}(b64chars);
|
||||
var fromCharCode = String.fromCharCode;
|
||||
// encoder stuff
|
||||
var cb_utob = function(c) {
|
||||
if (c.length < 2) {
|
||||
var cc = c.charCodeAt(0);
|
||||
return cc < 0x80 ? c
|
||||
: cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
|
||||
+ fromCharCode(0x80 | (cc & 0x3f)))
|
||||
: (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
|
||||
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
|
||||
+ fromCharCode(0x80 | ( cc & 0x3f)));
|
||||
} else {
|
||||
var cc = 0x10000
|
||||
+ (c.charCodeAt(0) - 0xD800) * 0x400
|
||||
+ (c.charCodeAt(1) - 0xDC00);
|
||||
return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
|
||||
+ fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
|
||||
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
|
||||
+ fromCharCode(0x80 | ( cc & 0x3f)));
|
||||
}
|
||||
};
|
||||
var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
|
||||
var utob = function(u) {
|
||||
return u.replace(re_utob, cb_utob);
|
||||
};
|
||||
var cb_encode = function(ccc) {
|
||||
var padlen = [0, 2, 1][ccc.length % 3],
|
||||
ord = ccc.charCodeAt(0) << 16
|
||||
| ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
|
||||
| ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
|
||||
chars = [
|
||||
b64chars.charAt( ord >>> 18),
|
||||
b64chars.charAt((ord >>> 12) & 63),
|
||||
padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
|
||||
padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
|
||||
];
|
||||
return chars.join('');
|
||||
};
|
||||
var btoa = global.btoa ? function(b) {
|
||||
return global.btoa(b);
|
||||
} : function(b) {
|
||||
return b.replace(/[\s\S]{1,3}/g, cb_encode);
|
||||
};
|
||||
var _encode = buffer ?
|
||||
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
|
||||
? function (u) {
|
||||
return (u.constructor === buffer.constructor ? u : buffer.from(u))
|
||||
.toString('base64')
|
||||
}
|
||||
: function (u) {
|
||||
return (u.constructor === buffer.constructor ? u : new buffer(u))
|
||||
.toString('base64')
|
||||
}
|
||||
: function (u) { return btoa(utob(u)) }
|
||||
;
|
||||
var encode = function(u, urisafe) {
|
||||
return !urisafe
|
||||
? _encode(String(u))
|
||||
: _encode(String(u)).replace(/[+\/]/g, function(m0) {
|
||||
return m0 == '+' ? '-' : '_';
|
||||
}).replace(/=/g, '');
|
||||
};
|
||||
var encodeURI = function(u) { return encode(u, true) };
|
||||
// decoder stuff
|
||||
var re_btou = new RegExp([
|
||||
'[\xC0-\xDF][\x80-\xBF]',
|
||||
'[\xE0-\xEF][\x80-\xBF]{2}',
|
||||
'[\xF0-\xF7][\x80-\xBF]{3}'
|
||||
].join('|'), 'g');
|
||||
var cb_btou = function(cccc) {
|
||||
switch(cccc.length) {
|
||||
case 4:
|
||||
var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
|
||||
| ((0x3f & cccc.charCodeAt(1)) << 12)
|
||||
| ((0x3f & cccc.charCodeAt(2)) << 6)
|
||||
| (0x3f & cccc.charCodeAt(3)),
|
||||
offset = cp - 0x10000;
|
||||
return (fromCharCode((offset >>> 10) + 0xD800)
|
||||
+ fromCharCode((offset & 0x3FF) + 0xDC00));
|
||||
case 3:
|
||||
return fromCharCode(
|
||||
((0x0f & cccc.charCodeAt(0)) << 12)
|
||||
| ((0x3f & cccc.charCodeAt(1)) << 6)
|
||||
| (0x3f & cccc.charCodeAt(2))
|
||||
);
|
||||
default:
|
||||
return fromCharCode(
|
||||
((0x1f & cccc.charCodeAt(0)) << 6)
|
||||
| (0x3f & cccc.charCodeAt(1))
|
||||
);
|
||||
}
|
||||
};
|
||||
var btou = function(b) {
|
||||
return b.replace(re_btou, cb_btou);
|
||||
};
|
||||
var cb_decode = function(cccc) {
|
||||
var len = cccc.length,
|
||||
padlen = len % 4,
|
||||
n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
|
||||
| (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
|
||||
| (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)
|
||||
| (len > 3 ? b64tab[cccc.charAt(3)] : 0),
|
||||
chars = [
|
||||
fromCharCode( n >>> 16),
|
||||
fromCharCode((n >>> 8) & 0xff),
|
||||
fromCharCode( n & 0xff)
|
||||
];
|
||||
chars.length -= [0, 0, 2, 1][padlen];
|
||||
return chars.join('');
|
||||
};
|
||||
var _atob = global.atob ? function(a) {
|
||||
return global.atob(a);
|
||||
} : function(a){
|
||||
return a.replace(/\S{1,4}/g, cb_decode);
|
||||
};
|
||||
var atob = function(a) {
|
||||
return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, ''));
|
||||
};
|
||||
var _decode = buffer ?
|
||||
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
|
||||
? function(a) {
|
||||
return (a.constructor === buffer.constructor
|
||||
? a : buffer.from(a, 'base64')).toString();
|
||||
}
|
||||
: function(a) {
|
||||
return (a.constructor === buffer.constructor
|
||||
? a : new buffer(a, 'base64')).toString();
|
||||
}
|
||||
: function(a) { return btou(_atob(a)) };
|
||||
var decode = function(a){
|
||||
return _decode(
|
||||
String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' })
|
||||
.replace(/[^A-Za-z0-9\+\/]/g, '')
|
||||
);
|
||||
};
|
||||
var noConflict = function() {
|
||||
var Base64 = global.Base64;
|
||||
global.Base64 = _Base64;
|
||||
return Base64;
|
||||
};
|
||||
// export Base64
|
||||
global.Base64 = {
|
||||
VERSION: version,
|
||||
atob: atob,
|
||||
btoa: btoa,
|
||||
fromBase64: decode,
|
||||
toBase64: encode,
|
||||
utob: utob,
|
||||
encode: encode,
|
||||
encodeURI: encodeURI,
|
||||
btou: btou,
|
||||
decode: decode,
|
||||
noConflict: noConflict,
|
||||
__buffer__: buffer
|
||||
};
|
||||
// if ES5 is available, make Base64.extendString() available
|
||||
if (typeof Object.defineProperty === 'function') {
|
||||
var noEnum = function(v){
|
||||
return {value:v,enumerable:false,writable:true,configurable:true};
|
||||
};
|
||||
global.Base64.extendString = function () {
|
||||
Object.defineProperty(
|
||||
String.prototype, 'fromBase64', noEnum(function () {
|
||||
return decode(this)
|
||||
}));
|
||||
Object.defineProperty(
|
||||
String.prototype, 'toBase64', noEnum(function (urisafe) {
|
||||
return encode(this, urisafe)
|
||||
}));
|
||||
Object.defineProperty(
|
||||
String.prototype, 'toBase64URI', noEnum(function () {
|
||||
return encode(this, true)
|
||||
}));
|
||||
};
|
||||
}
|
||||
//
|
||||
// export Base64 to the namespace
|
||||
//
|
||||
if (global['Meteor']) { // Meteor.js
|
||||
Base64 = global.Base64;
|
||||
}
|
||||
// module.exports and AMD are mutually exclusive.
|
||||
// module.exports has precedence.
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports.Base64 = global.Base64;
|
||||
}
|
||||
else if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define([], function(){ return global.Base64 });
|
||||
}
|
||||
// that's it!
|
||||
return {Base64: global.Base64}
|
||||
}));
|
||||
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./node_modules/_js-base64@2.5.1@js-base64/base64.js
|
||||
// module id = 57
|
||||
// module chunks = 2
|
||||
File diff suppressed because one or more lines are too long
@ -1,109 +0,0 @@
|
||||
/*
|
||||
* @Autor: lycheng
|
||||
* @Date: 2019-12-27 15:21:38
|
||||
* @Description:
|
||||
*/
|
||||
function writeString(data, offset, str) {
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
data.setUint8(offset + i, str.charCodeAt(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加wav头
|
||||
* @param {音频arrayBuffer} bytes
|
||||
* @param {采样率} sampleRate
|
||||
* @param {声道数} numChannels
|
||||
* @param {sampleBits} oututSampleBits
|
||||
* @param {小端字节} littleEdian
|
||||
*/
|
||||
function encodeWAV(
|
||||
bytes,
|
||||
sampleRate,
|
||||
numChannels,
|
||||
oututSampleBits,
|
||||
littleEdian = true
|
||||
) {
|
||||
let sampleBits = oututSampleBits
|
||||
let buffer = new ArrayBuffer(44 + bytes.byteLength)
|
||||
let data = new DataView(buffer)
|
||||
let channelCount = numChannels
|
||||
let offset = 0
|
||||
// 资源交换文件标识符
|
||||
writeString(data, offset, 'RIFF')
|
||||
offset += 4
|
||||
// 下个地址开始到文件尾总字节数,即文件大小-8
|
||||
data.setUint32(offset, 36 + bytes.byteLength, true)
|
||||
offset += 4
|
||||
// WAV文件标志
|
||||
writeString(data, offset, 'WAVE')
|
||||
offset += 4
|
||||
// 波形格式标志
|
||||
writeString(data, offset, 'fmt ')
|
||||
offset += 4
|
||||
// 过滤字节,一般为 0x10 = 16
|
||||
data.setUint32(offset, 16, true)
|
||||
offset += 4
|
||||
// 格式类别 (PCM形式采样数据)
|
||||
data.setUint16(offset, 1, true)
|
||||
offset += 2
|
||||
// 通道数
|
||||
data.setUint16(offset, channelCount, true)
|
||||
offset += 2
|
||||
// 采样率,每秒样本数,表示每个通道的播放速度
|
||||
data.setUint32(offset, sampleRate, true)
|
||||
offset += 4
|
||||
// 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
|
||||
data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true)
|
||||
offset += 4
|
||||
// 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
|
||||
data.setUint16(offset, channelCount * (sampleBits / 8), true)
|
||||
offset += 2
|
||||
// 每样本数据位数
|
||||
data.setUint16(offset, sampleBits, true)
|
||||
offset += 2
|
||||
// 数据标识符
|
||||
writeString(data, offset, 'data')
|
||||
offset += 4
|
||||
// 采样数据总数,即数据总大小-44
|
||||
data.setUint32(offset, bytes.byteLength, true)
|
||||
offset += 4
|
||||
|
||||
// 给wav头增加pcm体
|
||||
for (let i = 0; i < bytes.byteLength; ) {
|
||||
data.setUint8(offset, bytes.getUint8(i), true)
|
||||
offset++
|
||||
i++
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
function downloadWAV(audioData, sampleRate, oututSampleBits) {
|
||||
let wavData = encodeWAV(audioData, sampleRate||44100, 1, oututSampleBits||16)
|
||||
let blob = new Blob([wavData], {
|
||||
type: 'audio/wav',
|
||||
})
|
||||
let defaultName = new Date().getTime()
|
||||
let node = document.createElement('a')
|
||||
node.href = window.URL.createObjectURL(blob)
|
||||
node.download = `${defaultName}.wav`
|
||||
node.click()
|
||||
node.remove()
|
||||
}
|
||||
|
||||
function downloadPCM(audioData) {
|
||||
let blob = new Blob([audioData], {
|
||||
type: 'audio/pcm',
|
||||
})
|
||||
let defaultName = new Date().getTime()
|
||||
let node = document.createElement('a')
|
||||
node.href = window.URL.createObjectURL(blob)
|
||||
node.download = `${defaultName}.pcm`
|
||||
node.click()
|
||||
node.remove()
|
||||
}
|
||||
export {
|
||||
downloadWAV,
|
||||
downloadPCM
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
let minSampleRate = 22050
|
||||
self.onmessage = function(e) {
|
||||
transcode.transToAudioData(e.data)
|
||||
}
|
||||
var transcode = {
|
||||
transToAudioData: function(audioDataStr, fromRate = 16000, toRate = 22505) {
|
||||
let outputS16 = transcode.base64ToS16(audioDataStr)
|
||||
let output = transcode.transS16ToF32(outputS16)
|
||||
output = transcode.transSamplingRate(output, fromRate, toRate)
|
||||
output = Array.from(output)
|
||||
self.postMessage({
|
||||
data: output,
|
||||
rawAudioData: Array.from(outputS16)
|
||||
})
|
||||
},
|
||||
transSamplingRate: function(data, fromRate = 44100, toRate = 16000) {
|
||||
var fitCount = Math.round(data.length * (toRate / fromRate))
|
||||
var newData = new Float32Array(fitCount)
|
||||
var springFactor = (data.length - 1) / (fitCount - 1)
|
||||
newData[0] = data[0]
|
||||
for (let i = 1; i < fitCount - 1; i++) {
|
||||
var tmp = i * springFactor
|
||||
var before = Math.floor(tmp).toFixed()
|
||||
var after = Math.ceil(tmp).toFixed()
|
||||
var atPoint = tmp - before
|
||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
||||
}
|
||||
newData[fitCount - 1] = data[data.length - 1]
|
||||
return newData
|
||||
},
|
||||
transS16ToF32: function(input) {
|
||||
var tmpData = []
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
var d = input[i] < 0 ? input[i] / 0x8000 : input[i] / 0x7fff
|
||||
tmpData.push(d)
|
||||
}
|
||||
return new Float32Array(tmpData)
|
||||
},
|
||||
base64ToS16: function(base64AudioData) {
|
||||
base64AudioData = atob(base64AudioData)
|
||||
const outputArray = new Uint8Array(base64AudioData.length)
|
||||
for (let i = 0; i < base64AudioData.length; ++i) {
|
||||
outputArray[i] = base64AudioData.charCodeAt(i)
|
||||
}
|
||||
return new Int16Array(new DataView(outputArray.buffer).buffer)
|
||||
},
|
||||
}
|
||||
@ -1,347 +0,0 @@
|
||||
//
|
||||
// tts-ws.ts
|
||||
// 科大讯飞语音合成
|
||||
//
|
||||
// Created by panhong on 2023/07/20.
|
||||
//
|
||||
import { downloadPCM, downloadWAV } from './download.js';
|
||||
import CryptoES from "crypto-es"; // 科大讯飞
|
||||
import Enc from 'enc';
|
||||
const transWorker = new Worker(
|
||||
new URL("../kedatts/transcode.worker.js", import.meta.url)
|
||||
);
|
||||
import VConsole from 'vconsole';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
/**
|
||||
* 获取websocket url
|
||||
* APPID,APISecret,APIKey在控制台-我的应用-语音合成(流式版)页面获取,正式使用需要后端配置返回避免泄露
|
||||
*/
|
||||
const APPID = "";
|
||||
const API_SECRET = "";
|
||||
const API_KEY = "";
|
||||
|
||||
function getWebsocketUrl(): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
var apiKey = API_KEY;
|
||||
var apiSecret = API_SECRET;
|
||||
var url = 'wss://tts-api.xfyun.cn/v2/tts';
|
||||
var host = location.host;
|
||||
var date = new Date().toGMTString();
|
||||
var algorithm = 'hmac-sha256';
|
||||
var headers = 'host date request-line';
|
||||
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/tts HTTP/1.1`;
|
||||
var signatureSha = CryptoES.HmacSHA256(signatureOrigin, apiSecret);
|
||||
var signature = CryptoES.enc.Base64.stringify(signatureSha);
|
||||
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
|
||||
var authorization = btoa(authorizationOrigin);
|
||||
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
|
||||
resolve(url);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
class TTSRecorder {
|
||||
private speed: number;
|
||||
private voice: number;
|
||||
private pitch: number;
|
||||
private voiceName: string;
|
||||
private text: string;
|
||||
private tte: string;
|
||||
private defaultText: string;
|
||||
private appId: string;
|
||||
private audioData: number[];
|
||||
private rawAudioData: number[];
|
||||
private audioDataOffset: number;
|
||||
private status: string;
|
||||
private ttsWS: WebSocket;
|
||||
private playTimeout: ReturnType<typeof setTimeout> | null;
|
||||
private audioContext: AudioContext | null;
|
||||
private bufferSource: AudioBufferSourceNode | null;
|
||||
|
||||
constructor({
|
||||
speed = 50,
|
||||
voice = 50,
|
||||
pitch = 50,
|
||||
voiceName = 'xiaoyan',
|
||||
appId = APPID,
|
||||
text = '',
|
||||
tte = 'UTF8',
|
||||
defaultText = '请输入您要合成的文本',
|
||||
}: {
|
||||
speed?: number;
|
||||
voice?: number;
|
||||
pitch?: number;
|
||||
voiceName?: string;
|
||||
appId?: string;
|
||||
text?: string;
|
||||
tte?: string;
|
||||
defaultText?: string;
|
||||
} = {}) {
|
||||
this.speed = speed;
|
||||
this.voice = voice;
|
||||
this.pitch = pitch;
|
||||
this.voiceName = voiceName;
|
||||
this.text = text;
|
||||
this.tte = tte;
|
||||
this.defaultText = defaultText;
|
||||
this.appId = appId;
|
||||
this.audioData = [];
|
||||
this.rawAudioData = [];
|
||||
this.audioDataOffset = 0;
|
||||
this.status = 'init';
|
||||
this.ttsWS = null;
|
||||
this.playTimeout = null;
|
||||
this.audioContext = null;
|
||||
this.bufferSource = null;
|
||||
transWorker.onmessage = (e: MessageEvent) => {
|
||||
// if(e.data != undefined && e.data.length > 0) {
|
||||
this.audioData.push(...e.data.data);
|
||||
this.rawAudioData.push(...e.data.rawAudioData);
|
||||
// }
|
||||
};
|
||||
}
|
||||
|
||||
// 修改录音听写状态
|
||||
private setStatus(status: string) {
|
||||
if (this.onWillStatusChange) {
|
||||
this.onWillStatusChange(this.status, status);
|
||||
}
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
// 设置合成相关参数
|
||||
public setParams({ speed, voice, pitch, text, voiceName, tte }: {
|
||||
speed?: number;
|
||||
voice?: number;
|
||||
pitch?: number;
|
||||
text?: string;
|
||||
voiceName?: string;
|
||||
tte?: string;
|
||||
}) {
|
||||
speed !== undefined && (this.speed = speed);
|
||||
voice !== undefined && (this.voice = voice);
|
||||
pitch !== undefined && (this.pitch = pitch);
|
||||
text && (this.text = text);
|
||||
tte && (this.tte = tte);
|
||||
voiceName && (this.voiceName = voiceName);
|
||||
this.resetAudio();
|
||||
}
|
||||
|
||||
// 连接websocket
|
||||
private connectWebSocket() {
|
||||
this.setStatus('ttsing');
|
||||
return getWebsocketUrl().then(url => {
|
||||
let ttsWS: WebSocket;
|
||||
if ('WebSocket' in window) {
|
||||
ttsWS = new WebSocket(url);
|
||||
} else if ('MozWebSocket' in window) {
|
||||
ttsWS = new MozWebSocket(url);
|
||||
} else {
|
||||
alert('浏览器不支持WebSocket');
|
||||
return;
|
||||
}
|
||||
this.ttsWS = ttsWS;
|
||||
ttsWS.onopen = e => {
|
||||
this.webSocketSend();
|
||||
this.playTimeout = setTimeout(() => {
|
||||
this.audioPlay();
|
||||
}, 1000);
|
||||
};
|
||||
ttsWS.onmessage = e => {
|
||||
this.result(e.data);
|
||||
};
|
||||
ttsWS.onerror = e => {
|
||||
clearTimeout(this.playTimeout);
|
||||
this.setStatus('errorTTS');
|
||||
alert('WebSocket报错,请f12查看详情');
|
||||
console.error(`详情查看:${encodeURI(url.replace('wss:', 'https:'))}`);
|
||||
};
|
||||
ttsWS.onclose = e => {
|
||||
console.log(e);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 处理音频数据
|
||||
private transToAudioData(audioData: number[]) {
|
||||
// Implement your logic here
|
||||
}
|
||||
|
||||
// websocket发送数据
|
||||
// bgs有背景音 0:无背景音(默认值) 1:有背景音
|
||||
// auf 音频采样率,可选值:
|
||||
// audio/L16;rate=8000:合成8K 的音频
|
||||
// audio/L16;rate=16000:合成16K 的音频
|
||||
// auf不传值:合成16K 的音频
|
||||
// aue:音频编码,可选值:
|
||||
// raw:未压缩的pcm
|
||||
// lame:mp3 (当aue=lame时需传参sfl=1)
|
||||
// speex-org-wb;7: 标准开源speex(for speex_wideband,即16k)数字代表指定压缩等级(默认等级为8)
|
||||
// speex-org-nb;7: 标准开源speex(for speex_narrowband,即8k)数字代表指定压缩等级(默认等级为8)
|
||||
// speex;7:压缩格式,压缩等级1~10,默认为7(8k讯飞定制speex)
|
||||
// speex-wb;7:压缩格式,压缩等级1~10,默认为7(16k讯飞定制speex)
|
||||
private webSocketSend() {
|
||||
var params = {
|
||||
common: {
|
||||
app_id: this.appId, // APPID
|
||||
},
|
||||
business: {
|
||||
aue: 'raw',
|
||||
auf: 'audio/L16;rate=16000',
|
||||
vcn: this.voiceName,
|
||||
speed: this.speed,
|
||||
volume: this.voice,
|
||||
pitch: this.pitch,
|
||||
bgs: 0,
|
||||
tte: this.tte,
|
||||
},
|
||||
data: {
|
||||
status: 2,
|
||||
text: this.encodeText(
|
||||
this.text || this.defaultText,
|
||||
this.tte === 'unicode' ? 'base64&utf16le' : ''
|
||||
),
|
||||
},
|
||||
};
|
||||
this.ttsWS.send(JSON.stringify(params));
|
||||
}
|
||||
|
||||
private encodeText(text: string, encoding: string): ArrayBuffer | string {
|
||||
switch (encoding) {
|
||||
case 'utf16le': {
|
||||
let buf = new ArrayBuffer(text.length * 4);
|
||||
let bufView = new Uint16Array(buf);
|
||||
for (let i = 0, strlen = text.length; i < strlen; i++) {
|
||||
bufView[i] = text.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
case 'buffer2Base64': {
|
||||
let binary = '';
|
||||
let bytes = new Uint8Array(text);
|
||||
let len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
}
|
||||
case 'base64&utf16le': {
|
||||
return this.encodeText(
|
||||
this.encodeText(text, 'utf16le') as string,
|
||||
'buffer2Base64'
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return Base64.encode(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// websocket接收数据的处理
|
||||
private result(resultData: string) {
|
||||
let jsonData = JSON.parse(resultData);
|
||||
// 合成失败
|
||||
if (jsonData.code !== 0) {
|
||||
alert(`合成失败: ${jsonData.code}:${jsonData.message}`);
|
||||
console.error(`${jsonData.code}:${jsonData.message}`);
|
||||
this.resetAudio();
|
||||
return;
|
||||
}
|
||||
transWorker.postMessage(jsonData.data.audio);
|
||||
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
||||
this.ttsWS.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 重置音频数据
|
||||
private resetAudio() {
|
||||
this.audioStop();
|
||||
this.setStatus('init');
|
||||
this.audioDataOffset = 0;
|
||||
this.audioData = [];
|
||||
this.rawAudioData = [];
|
||||
this.ttsWS && this.ttsWS.close();
|
||||
clearTimeout(this.playTimeout);
|
||||
}
|
||||
|
||||
// 音频初始化
|
||||
private audioInit() {
|
||||
let AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
if (AudioContext) {
|
||||
this.audioContext = new AudioContext();
|
||||
this.audioContext.resume();
|
||||
this.audioDataOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 音频播放
|
||||
private audioPlay() {
|
||||
this.setStatus('play');
|
||||
let audioData = this.audioData.slice(this.audioDataOffset);
|
||||
this.audioDataOffset += audioData.length;
|
||||
let audioBuffer = this.audioContext.createBuffer(1, audioData.length, 22050);
|
||||
let nowBuffering = audioBuffer.getChannelData(0);
|
||||
if (audioBuffer.copyToChannel) {
|
||||
audioBuffer.copyToChannel(
|
||||
new Float32Array(audioData),
|
||||
0,
|
||||
0
|
||||
);
|
||||
} else {
|
||||
for (let i = 0; i < audioData.length; i++) {
|
||||
nowBuffering[i] = audioData[i];
|
||||
}
|
||||
}
|
||||
let bufferSource = this.bufferSource = this.audioContext.createBufferSource();
|
||||
bufferSource.buffer = audioBuffer;
|
||||
bufferSource.connect(this.audioContext.destination);
|
||||
bufferSource.start();
|
||||
bufferSource.onended = event => {
|
||||
if (this.status !== 'play') {
|
||||
return;
|
||||
}
|
||||
if (this.audioDataOffset < this.audioData.length) {
|
||||
this.audioPlay();
|
||||
} else {
|
||||
this.audioStop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 音频播放结束
|
||||
private audioStop() {
|
||||
this.setStatus('endPlay');
|
||||
clearTimeout(this.playTimeout);
|
||||
this.audioDataOffset = 0;
|
||||
if (this.bufferSource) {
|
||||
try {
|
||||
this.bufferSource.stop();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public start() {
|
||||
if (this.audioData.length) {
|
||||
this.audioPlay();
|
||||
} else {
|
||||
if (!this.audioContext) {
|
||||
this.audioInit();
|
||||
}
|
||||
if (!this.audioContext) {
|
||||
alert('该浏览器不支持webAudioApi相关接口');
|
||||
return;
|
||||
}
|
||||
this.connectWebSocket();
|
||||
}
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.audioStop();
|
||||
}
|
||||
}
|
||||
|
||||
export default TTSRecorder;
|
||||
@ -1,243 +0,0 @@
|
||||
/*
|
||||
* base64.js
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License.
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* References:
|
||||
* http://en.wikipedia.org/wiki/Base64
|
||||
*/
|
||||
;(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
? module.exports = factory(global)
|
||||
: typeof define === 'function' && define.amd
|
||||
? define(factory) : factory(global)
|
||||
}((
|
||||
typeof self !== 'undefined' ? self
|
||||
: typeof window !== 'undefined' ? window
|
||||
: typeof global !== 'undefined' ? global
|
||||
: this
|
||||
), function(global) {
|
||||
'use strict';
|
||||
// existing version for noConflict()
|
||||
global = global || {};
|
||||
var _Base64 = global.Base64;
|
||||
var version = "2.5.1";
|
||||
// if node.js and NOT React Native, we use Buffer
|
||||
var buffer;
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
try {
|
||||
buffer = eval("require('buffer').Buffer");
|
||||
} catch (err) {
|
||||
buffer = undefined;
|
||||
}
|
||||
}
|
||||
// constants
|
||||
var b64chars
|
||||
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
var b64tab = function(bin) {
|
||||
var t = {};
|
||||
for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
|
||||
return t;
|
||||
}(b64chars);
|
||||
var fromCharCode = String.fromCharCode;
|
||||
// encoder stuff
|
||||
var cb_utob = function(c) {
|
||||
if (c.length < 2) {
|
||||
var cc = c.charCodeAt(0);
|
||||
return cc < 0x80 ? c
|
||||
: cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
|
||||
+ fromCharCode(0x80 | (cc & 0x3f)))
|
||||
: (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
|
||||
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
|
||||
+ fromCharCode(0x80 | ( cc & 0x3f)));
|
||||
} else {
|
||||
var cc = 0x10000
|
||||
+ (c.charCodeAt(0) - 0xD800) * 0x400
|
||||
+ (c.charCodeAt(1) - 0xDC00);
|
||||
return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
|
||||
+ fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
|
||||
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
|
||||
+ fromCharCode(0x80 | ( cc & 0x3f)));
|
||||
}
|
||||
};
|
||||
var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
|
||||
var utob = function(u) {
|
||||
return u.replace(re_utob, cb_utob);
|
||||
};
|
||||
var cb_encode = function(ccc) {
|
||||
var padlen = [0, 2, 1][ccc.length % 3],
|
||||
ord = ccc.charCodeAt(0) << 16
|
||||
| ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
|
||||
| ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
|
||||
chars = [
|
||||
b64chars.charAt( ord >>> 18),
|
||||
b64chars.charAt((ord >>> 12) & 63),
|
||||
padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
|
||||
padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
|
||||
];
|
||||
return chars.join('');
|
||||
};
|
||||
var btoa = global.btoa ? function(b) {
|
||||
return global.btoa(b);
|
||||
} : function(b) {
|
||||
return b.replace(/[\s\S]{1,3}/g, cb_encode);
|
||||
};
|
||||
var _encode = buffer ?
|
||||
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
|
||||
? function (u) {
|
||||
return (u.constructor === buffer.constructor ? u : buffer.from(u))
|
||||
.toString('base64')
|
||||
}
|
||||
: function (u) {
|
||||
return (u.constructor === buffer.constructor ? u : new buffer(u))
|
||||
.toString('base64')
|
||||
}
|
||||
: function (u) { return btoa(utob(u)) }
|
||||
;
|
||||
var encode = function(u, urisafe) {
|
||||
return !urisafe
|
||||
? _encode(String(u))
|
||||
: _encode(String(u)).replace(/[+\/]/g, function(m0) {
|
||||
return m0 == '+' ? '-' : '_';
|
||||
}).replace(/=/g, '');
|
||||
};
|
||||
var encodeURI = function(u) { return encode(u, true) };
|
||||
// decoder stuff
|
||||
var re_btou = new RegExp([
|
||||
'[\xC0-\xDF][\x80-\xBF]',
|
||||
'[\xE0-\xEF][\x80-\xBF]{2}',
|
||||
'[\xF0-\xF7][\x80-\xBF]{3}'
|
||||
].join('|'), 'g');
|
||||
var cb_btou = function(cccc) {
|
||||
switch(cccc.length) {
|
||||
case 4:
|
||||
var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
|
||||
| ((0x3f & cccc.charCodeAt(1)) << 12)
|
||||
| ((0x3f & cccc.charCodeAt(2)) << 6)
|
||||
| (0x3f & cccc.charCodeAt(3)),
|
||||
offset = cp - 0x10000;
|
||||
return (fromCharCode((offset >>> 10) + 0xD800)
|
||||
+ fromCharCode((offset & 0x3FF) + 0xDC00));
|
||||
case 3:
|
||||
return fromCharCode(
|
||||
((0x0f & cccc.charCodeAt(0)) << 12)
|
||||
| ((0x3f & cccc.charCodeAt(1)) << 6)
|
||||
| (0x3f & cccc.charCodeAt(2))
|
||||
);
|
||||
default:
|
||||
return fromCharCode(
|
||||
((0x1f & cccc.charCodeAt(0)) << 6)
|
||||
| (0x3f & cccc.charCodeAt(1))
|
||||
);
|
||||
}
|
||||
};
|
||||
var btou = function(b) {
|
||||
return b.replace(re_btou, cb_btou);
|
||||
};
|
||||
var cb_decode = function(cccc) {
|
||||
var len = cccc.length,
|
||||
padlen = len % 4,
|
||||
n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
|
||||
| (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
|
||||
| (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)
|
||||
| (len > 3 ? b64tab[cccc.charAt(3)] : 0),
|
||||
chars = [
|
||||
fromCharCode( n >>> 16),
|
||||
fromCharCode((n >>> 8) & 0xff),
|
||||
fromCharCode( n & 0xff)
|
||||
];
|
||||
chars.length -= [0, 0, 2, 1][padlen];
|
||||
return chars.join('');
|
||||
};
|
||||
var _atob = global.atob ? function(a) {
|
||||
return global.atob(a);
|
||||
} : function(a){
|
||||
return a.replace(/\S{1,4}/g, cb_decode);
|
||||
};
|
||||
var atob = function(a) {
|
||||
return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, ''));
|
||||
};
|
||||
var _decode = buffer ?
|
||||
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
|
||||
? function(a) {
|
||||
return (a.constructor === buffer.constructor
|
||||
? a : buffer.from(a, 'base64')).toString();
|
||||
}
|
||||
: function(a) {
|
||||
return (a.constructor === buffer.constructor
|
||||
? a : new buffer(a, 'base64')).toString();
|
||||
}
|
||||
: function(a) { return btou(_atob(a)) };
|
||||
var decode = function(a){
|
||||
return _decode(
|
||||
String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' })
|
||||
.replace(/[^A-Za-z0-9\+\/]/g, '')
|
||||
);
|
||||
};
|
||||
var noConflict = function() {
|
||||
var Base64 = global.Base64;
|
||||
global.Base64 = _Base64;
|
||||
return Base64;
|
||||
};
|
||||
// export Base64
|
||||
global.Base64 = {
|
||||
VERSION: version,
|
||||
atob: atob,
|
||||
btoa: btoa,
|
||||
fromBase64: decode,
|
||||
toBase64: encode,
|
||||
utob: utob,
|
||||
encode: encode,
|
||||
encodeURI: encodeURI,
|
||||
btou: btou,
|
||||
decode: decode,
|
||||
noConflict: noConflict,
|
||||
__buffer__: buffer
|
||||
};
|
||||
// if ES5 is available, make Base64.extendString() available
|
||||
if (typeof Object.defineProperty === 'function') {
|
||||
var noEnum = function(v){
|
||||
return {value:v,enumerable:false,writable:true,configurable:true};
|
||||
};
|
||||
global.Base64.extendString = function () {
|
||||
Object.defineProperty(
|
||||
String.prototype, 'fromBase64', noEnum(function () {
|
||||
return decode(this)
|
||||
}));
|
||||
Object.defineProperty(
|
||||
String.prototype, 'toBase64', noEnum(function (urisafe) {
|
||||
return encode(this, urisafe)
|
||||
}));
|
||||
Object.defineProperty(
|
||||
String.prototype, 'toBase64URI', noEnum(function () {
|
||||
return encode(this, true)
|
||||
}));
|
||||
};
|
||||
}
|
||||
//
|
||||
// export Base64 to the namespace
|
||||
//
|
||||
if (global['Meteor']) { // Meteor.js
|
||||
Base64 = global.Base64;
|
||||
}
|
||||
// module.exports and AMD are mutually exclusive.
|
||||
// module.exports has precedence.
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports.Base64 = global.Base64;
|
||||
}
|
||||
else if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define([], function(){ return global.Base64 });
|
||||
}
|
||||
// that's it!
|
||||
return {Base64: global.Base64}
|
||||
}));
|
||||
|
||||
|
||||
|
||||
//////////////////
|
||||
// WEBPACK FOOTER
|
||||
// ./node_modules/_js-base64@2.5.1@js-base64/base64.js
|
||||
// module id = 57
|
||||
// module chunks = 2
|
||||
File diff suppressed because one or more lines are too long
@ -1,362 +0,0 @@
|
||||
//
|
||||
// iatRecorder.ts
|
||||
// 科大讯飞语音识别
|
||||
//
|
||||
// Created by panhong on 2023/07/20.
|
||||
//
|
||||
import CryptoES from "crypto-es"; // 科大讯飞
|
||||
const transWorker = new Worker(
|
||||
new URL("../until/transcode.worker.js", import.meta.url)
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取websocket url
|
||||
* APPID,APISecret,APIKey在控制台-我的应用-语音合成(流式版)页面获取,正式使用需要后端配置返回避免泄露
|
||||
*/
|
||||
const APPID = "";
|
||||
const API_SECRET = "";
|
||||
const API_KEY = "";
|
||||
|
||||
const getWebSocketUrl = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 请求地址根据语种不同变化
|
||||
var url = "wss://iat-api.xfyun.cn/v2/iat";
|
||||
var host = "iat-api.xfyun.cn";
|
||||
var apiKey = API_KEY;
|
||||
var apiSecret = API_SECRET;
|
||||
var date = new Date().toGMTString();
|
||||
var algorithm = "hmac-sha256";
|
||||
var headers = "host date request-line";
|
||||
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`;
|
||||
var signatureSha = CryptoES.HmacSHA256(signatureOrigin, apiSecret);
|
||||
var signature = CryptoES.enc.Base64.stringify(signatureSha);
|
||||
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
|
||||
var authorization = btoa(authorizationOrigin);
|
||||
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
|
||||
resolve(url);
|
||||
});
|
||||
};
|
||||
|
||||
class IatRecorder {
|
||||
public status: string;
|
||||
public accent: string;
|
||||
public language: string;
|
||||
public appId: string;
|
||||
public audioData: any[];
|
||||
public resultText: string;
|
||||
public resultTextTemp: string;
|
||||
public onWillStatusChange?: (
|
||||
prevStatus: string,
|
||||
nextStatus: string
|
||||
) => void;
|
||||
public onTextChange?: (text: string) => void;
|
||||
constructor(
|
||||
{ language, accent, appId } = {} as {
|
||||
language?: string;
|
||||
accent?: string;
|
||||
appId?: string;
|
||||
}
|
||||
) {
|
||||
let self = this;
|
||||
this.status = "null";
|
||||
this.language = language || "zh_cn";
|
||||
this.accent = accent || "mandarin";
|
||||
this.appId = appId || APPID;
|
||||
// 记录音频数据
|
||||
this.audioData = [];
|
||||
// 记录听写结果
|
||||
this.resultText = "";
|
||||
// wpgs下的听写结果需要中间状态辅助记录
|
||||
this.resultTextTemp = "";
|
||||
transWorker.onmessage = function (event) {
|
||||
self.audioData.push(...event.data);
|
||||
};
|
||||
}
|
||||
// 修改录音听写状态
|
||||
setStatus(status:string) {
|
||||
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status)
|
||||
this.status = status
|
||||
}
|
||||
// setStatus({ status, resultText, resultTextTemp }:{ string; resultText?: string; resultTextTemp?: string}) {
|
||||
// this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status, resultTextTemp || resultText || '')
|
||||
// this.status = status
|
||||
// resultText !== undefined && (this.resultText = resultText)
|
||||
// resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
|
||||
// }
|
||||
setResultText({ resultText, resultTextTemp }:{ resultText?: string; resultTextTemp?: string } = {}) {
|
||||
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '')
|
||||
resultText !== undefined && (this.resultText = resultText)
|
||||
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
|
||||
}
|
||||
|
||||
// 修改听写参数
|
||||
setParams({ language, accent }:{language?:string; accent?:string} = {}) {
|
||||
language && (this.language = language)
|
||||
accent && (this.accent = accent)
|
||||
}
|
||||
|
||||
// 连接websocket
|
||||
connectWebSocket() {
|
||||
return getWebSocketUrl().then(url => {
|
||||
let iatWS
|
||||
if ('WebSocket' in window) {
|
||||
iatWS = new WebSocket(url)
|
||||
} else if ('MozWebSocket' in window) {
|
||||
iatWS = new MozWebSocket(url)
|
||||
} else {
|
||||
alert('浏览器不支持WebSocket')
|
||||
return
|
||||
}
|
||||
this.webSocket = iatWS
|
||||
this.setStatus('init')
|
||||
iatWS.onopen = e => {
|
||||
this.setStatus('ing')
|
||||
// 重新开始录音
|
||||
setTimeout(() => {
|
||||
this.webSocketSend()
|
||||
}, 500)
|
||||
}
|
||||
iatWS.onmessage = e => {
|
||||
this.result(e.data)
|
||||
}
|
||||
iatWS.onerror = e => {
|
||||
console.log(`${e.code}`,'onerroronerroronerror')
|
||||
this.recorderStop()
|
||||
}
|
||||
iatWS.onclose = e => {
|
||||
console.log(`${e.code}`,'oncloseonclose')
|
||||
this.recorderStop()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化浏览器录音
|
||||
recorderInit() {
|
||||
navigator.getUserMedia =
|
||||
navigator.getUserMedia ||
|
||||
navigator.webkitGetUserMedia ||
|
||||
navigator.mozGetUserMedia ||
|
||||
navigator.msGetUserMedia
|
||||
|
||||
// 创建音频环境
|
||||
try {
|
||||
this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
||||
this.audioContext.resume()
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口')
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取浏览器录音权限
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({
|
||||
audio: true,
|
||||
video: false,
|
||||
})
|
||||
.then(stream => {
|
||||
getMediaSuccess(stream)
|
||||
})
|
||||
.catch(e => {
|
||||
getMediaFail(e)
|
||||
})
|
||||
} else if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia(
|
||||
{
|
||||
audio: true,
|
||||
video: false,
|
||||
},
|
||||
stream => {
|
||||
getMediaSuccess(stream)
|
||||
},
|
||||
function (e) {
|
||||
getMediaFail(e)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) {
|
||||
alert('chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限')
|
||||
} else {
|
||||
alert('无法获取浏览器录音功能,请升级浏览器或使用chrome')
|
||||
}
|
||||
this.audioContext && this.audioContext.close()
|
||||
return
|
||||
}
|
||||
// 获取浏览器录音权限成功的回调
|
||||
let getMediaSuccess = stream => {
|
||||
console.log('getMediaSuccess')
|
||||
// 创建一个用于通过JavaScript直接处理音频
|
||||
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
|
||||
this.scriptProcessor.onaudioprocess = e => {
|
||||
// 去处理音频数据
|
||||
if (this.status === 'ing') {
|
||||
transWorker.postMessage(e.inputBuffer.getChannelData(0))
|
||||
}
|
||||
}
|
||||
// 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
|
||||
this.mediaSource = this.audioContext.createMediaStreamSource(stream)
|
||||
// 连接
|
||||
this.mediaSource.connect(this.scriptProcessor)
|
||||
this.scriptProcessor.connect(this.audioContext.destination)
|
||||
this.connectWebSocket()
|
||||
}
|
||||
|
||||
let getMediaFail = (e) => {
|
||||
alert('请求麦克风失败')
|
||||
console.log(e)
|
||||
this.audioContext && this.audioContext.close()
|
||||
this.audioContext = undefined
|
||||
// 关闭websocket
|
||||
if (this.webSocket && this.webSocket.readyState === 1) {
|
||||
this.webSocket.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
recorderStart() {
|
||||
if (!this.audioContext) {
|
||||
this.recorderInit()
|
||||
} else {
|
||||
this.audioContext.resume()
|
||||
this.connectWebSocket()
|
||||
}
|
||||
}
|
||||
// 暂停录音
|
||||
recorderStop() {
|
||||
// safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend
|
||||
if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))) {
|
||||
this.audioContext && this.audioContext.suspend()
|
||||
}
|
||||
this.setStatus('end')
|
||||
}
|
||||
// 处理音频数据
|
||||
// transAudioData(audioData) {
|
||||
// audioData = transAudioData.transaction(audioData)
|
||||
// this.audioData.push(...audioData)
|
||||
// }
|
||||
// 对处理后的音频数据进行base64编码,
|
||||
toBase64(buffer) {
|
||||
var binary = ''
|
||||
var bytes = new Uint8Array(buffer)
|
||||
var len = bytes.byteLength
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i])
|
||||
}
|
||||
return window.btoa(binary)
|
||||
}
|
||||
// 向webSocket发送数据
|
||||
webSocketSend() {
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
return
|
||||
}
|
||||
let audioData = this.audioData.splice(0, 1280)
|
||||
var params = {
|
||||
common: {
|
||||
app_id: this.appId,
|
||||
},
|
||||
business: {
|
||||
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
domain: 'iat',
|
||||
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
vad_eos: 5000,
|
||||
dwa: 'wpgs', //为使该功能生效,需到控制台开通动态修正功能(该功能免费)
|
||||
},
|
||||
data: {
|
||||
status: 0,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData),
|
||||
},
|
||||
}
|
||||
console.log(audioData, 'audioData')
|
||||
this.webSocket.send(JSON.stringify(params))
|
||||
this.handlerInterval = setInterval(() => {
|
||||
// websocket未连接
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
this.audioData = []
|
||||
clearInterval(this.handlerInterval)
|
||||
return
|
||||
}
|
||||
if (this.audioData.length === 0) {
|
||||
if (this.status === 'end') {
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 2,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: '',
|
||||
},
|
||||
})
|
||||
)
|
||||
this.audioData = []
|
||||
clearInterval(this.handlerInterval)
|
||||
}
|
||||
return false
|
||||
}
|
||||
audioData = this.audioData.splice(0, 1280)
|
||||
// 中间帧
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 1,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData),
|
||||
},
|
||||
})
|
||||
)
|
||||
}, 40)
|
||||
}
|
||||
result(resultData) {
|
||||
// 识别结束
|
||||
let jsonData = JSON.parse(resultData)
|
||||
if (jsonData.data && jsonData.data.result) {
|
||||
let data = jsonData.data.result
|
||||
let str = ''
|
||||
let resultStr = ''
|
||||
let ws = data.ws
|
||||
for (let i = 0; i < ws.length; i++) {
|
||||
str = str + ws[i].cw[0].w
|
||||
}
|
||||
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
|
||||
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
|
||||
if (data.pgs) {
|
||||
if (data.pgs === 'apd') {
|
||||
// 将resultTextTemp同步给resultText
|
||||
this.setResultText({
|
||||
resultText: this.resultTextTemp,
|
||||
})
|
||||
}
|
||||
// 将结果存储在resultTextTemp中
|
||||
this.setResultText({
|
||||
resultTextTemp: this.resultText + str,
|
||||
})
|
||||
} else {
|
||||
this.setResultText({
|
||||
resultText: this.resultText + str,
|
||||
})
|
||||
}
|
||||
}
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
||||
this.webSocket.close()
|
||||
}
|
||||
if (jsonData.code !== 0) {
|
||||
this.webSocket.close()
|
||||
console.log(`${jsonData.code}:${jsonData.message}`)
|
||||
}
|
||||
}
|
||||
start() {
|
||||
this.recorderStart()
|
||||
this.setResultText({ resultText: '', resultTextTemp: '' })
|
||||
}
|
||||
stop() {
|
||||
this.recorderStop()
|
||||
}
|
||||
}
|
||||
export default IatRecorder
|
||||
@ -1,41 +0,0 @@
|
||||
|
||||
|
||||
self.onmessage = function(e){
|
||||
transAudioData.transcode(e.data)
|
||||
}
|
||||
let transAudioData = {
|
||||
transcode(audioData) {
|
||||
let output = transAudioData.to16kHz(audioData)
|
||||
output = transAudioData.to16BitPCM(output)
|
||||
output = Array.from(new Uint8Array(output.buffer))
|
||||
self.postMessage(output)
|
||||
// return output
|
||||
},
|
||||
to16kHz(audioData) {
|
||||
var data = new Float32Array(audioData)
|
||||
var fitCount = Math.round(data.length * (16000 / 44100))
|
||||
var newData = new Float32Array(fitCount)
|
||||
var springFactor = (data.length - 1) / (fitCount - 1)
|
||||
newData[0] = data[0]
|
||||
for (let i = 1; i < fitCount - 1; i++) {
|
||||
var tmp = i * springFactor
|
||||
var before = Math.floor(tmp).toFixed()
|
||||
var after = Math.ceil(tmp).toFixed()
|
||||
var atPoint = tmp - before
|
||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
||||
}
|
||||
newData[fitCount - 1] = data[data.length - 1]
|
||||
return newData
|
||||
},
|
||||
to16BitPCM(input) {
|
||||
var dataLength = input.length * (16 / 8)
|
||||
var dataBuffer = new ArrayBuffer(dataLength)
|
||||
var dataView = new DataView(dataBuffer)
|
||||
var offset = 0
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
var s = Math.max(-1, Math.min(1, input[i]))
|
||||
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
|
||||
}
|
||||
return dataView
|
||||
},
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user