merge dev_fastchat

This commit is contained in:
imClumsyPanda 2023-08-12 16:31:20 +08:00
parent 18fe1b97f9
commit 90eb45ac46
9 changed files with 0 additions and 1452 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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)
},
}

View File

@ -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
* APPIDAPISecretAPIKey在控制台--使
*/
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
// lamemp3 (当aue=lame时需传参sfl=1)
// speex-org-wb;7 标准开源speexfor speex_wideband即16k数字代表指定压缩等级默认等级为8
// speex-org-nb;7 标准开源speexfor speex_narrowband即8k数字代表指定压缩等级默认等级为8
// speex;7压缩格式压缩等级1~10默认为78k讯飞定制speex
// speex-wb;7压缩格式压缩等级1~10默认为716k讯飞定制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;

View File

@ -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

View File

@ -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
* APPIDAPISecretAPIKey在控制台--使
*/
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

View File

@ -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
},
}