record.js文件(流式输出关键文件)(参考博客:vue使用websocket语音识别连续流式输出_recorder-core-csdn博客) //recorder
import recorder from 'recorder-core'
// type
import 'recorder-core/src/engine/pcm.js'
//可选的扩展支持项
import 'recorder-core/src/extensions/wavesurfer.view.js'
export const jsrecorder = (cb) => {
var testsamplerate = 16000
var testbitrate = 16
var sendframesize = 1280
// 去掉console
recorder.clog = () => {}
//重置环境,每次开始录音时必须先调用此方法,清理环境
var realtimesendtryreset = function () {
realtimesendtrychunks = null
}
var realtimesendtrynumber
var transferuploadnumbermax
var realtimesendtrychunk
var realtimesendtrychunks
//=====实时处理核心函数==========
var realtimesendtry = function (buffers, buffersamplerate, isclose) {
if (realtimesendtrychunks == null) {
realtimesendtrynumber = 0
transferuploadnumbermax = 0
realtimesendtrychunk = null
realtimesendtrychunks = []
}
//配置有效性检查
if (testbitrate == 16 && sendframesize % 2 == 1) {
return
}
var pcm = [],
pcmsamplerate = 0
if (buffers.length > 0) {
//借用sampledata函数进行数据的连续处理,采样率转换是顺带的,得到新的pcm数据
var chunk = recorder.sampledata(
buffers,
buffersamplerate,
testsamplerate,
realtimesendtrychunk
)
//清理已处理完的缓冲数据,释放内存以支持长时间录音,最后完成录音时不能调用stop,因为数据已经被清掉了
for (
var i = realtimesendtrychunk ? realtimesendtrychunk.index : 0;
i < chunk.index - 3;
i++
) {
buffers[i] = null
}
realtimesendtrychunk = chunk //此时的chunk.data就是原始的音频16位pcm数据(小端le),直接保存即为16位pcm文件、加个wav头即为wav文件、丢给mp3编码器转一下码即为mp3文件
pcm = chunk.data
pcmsamplerate = chunk.samplerate
if (pcmsamplerate != testsamplerate)
//除非是onprocess给的buffersamplerate低于testsamplerate
throw new error(
'不应该出现pcm采样率' +
pcmsamplerate +
'和需要的采样率' +
testsamplerate +
'不一致'
)
}
//将pcm数据丢进缓冲,凑够一帧发送,缓冲内的数据可能有多帧,循环切分发送
if (pcm.length > 0) {
realtimesendtrychunks.push({ pcm: pcm, pcmsamplerate: pcmsamplerate })
}
//从缓冲中切出一帧数据
var chunksize = sendframesize / (testbitrate / 8) //8位时需要的采样数和帧大小一致,16位时采样数为帧大小的一半
pcm = new int16array(chunksize)
pcmsamplerate = 0
var pcmok = false,
pcmlen = 0
for1: for (var i1 = 0; i1 < realtimesendtrychunks.length; i1++) {
chunk = realtimesendtrychunks[i1]
pcmsamplerate = chunk.pcmsamplerate
for (var i2 = chunk.offset || 0; i2 < chunk.pcm.length; i2++) {
pcm[pcmlen] = chunk.pcm[i2]
pcmlen++
//满一帧了,清除已消费掉的缓冲
if (pcmlen == chunksize) {
pcmok = true
chunk.offset = i2 + 1
for (var i3 = 0; i3 < i1; i3++) {
realtimesendtrychunks.splice(0, 1)
}
break for1
}
}
}
//缓冲的数据不够一帧时,不发送 或者 是结束了
if (!pcmok) {
if (isclose) {
var number = ++realtimesendtrynumber
transferupload(number, null, 0, null, isclose)
}
return
}
//16位pcm格式可以不经过mock转码,直接发送new blob([pcm.buffer],{type:"audio/pcm"}) 但8位的就必须转码,通用起见,均转码处理,pcm转码速度极快
number = ++realtimesendtrynumber
var encstarttime = date.now()
var recmock = recorder({
type: 'pcm',
samplerate: testsamplerate, //需要转换成的采样率
bitrate: testbitrate //需要转换成的比特率
})
recmock.mock(pcm, pcmsamplerate)
recmock.stop(
function (blob, duration) {
blob.enctime = date.now() - encstarttime
//转码好就推入传输
transferupload(number, blob, duration, recmock, false)
//循环调用,继续切分缓冲中的数据帧,直到不够一帧
realtimesendtry([], 0, isclose)
},
function (msg) {
//转码错误?没想到什么时候会产生错误!
}
)
}
//=====数据传输函数==========
var transferupload = function (
number,
blobornull,
duration,
blobrec,
isclose
) {
// console.log(number, blobornull, duration, blobrec, isclose)
transferuploadnumbermax = math.max(transferuploadnumbermax, number)
if (blobornull) {
var blob = blobornull
var enctime = blob.enctime
cb(blob, enctime)
}
if (isclose) {
// console.log('isclose')
}
}
//调用录音
var rec
var wave
function recstart() {
if (rec) {
rec.close()
}
rec = recorder({
type: 'unknown',
onprocess: function (
buffers,
powerlevel,
bufferduration,
buffersamplerate
) {
//推入实时处理,因为是unknown格式,buffers和rec.buffers是完全相同的,只需清理buffers就能释放内存。
realtimesendtry(buffers, buffersamplerate, false)
//可视化图形绘制
// wave.input(buffers[buffers.length - 1], powerlevel, buffersamplerate);
}
})
// var t = settimeout(function () {
// console.log('无法录音:权限请求被忽略(超时假装手动点击了确认对话框)', 1)
// }, 8000)
rec.open(function () {
//打开麦克风授权获得相关资源
rec.start() //开始录音
realtimesendtryreset() //重置环境,开始录音时必须调用一次
//此处创建这些音频可视化图形绘制浏览器支持妥妥的
// wave = recorder.wavesurferview({
// elem: ".speak-wave",
// height: 30 //显示宽度
// , scale: 2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
// , fps: 50 //绘制帧率,不可过高,50-60fps运动性质动画明显会流畅舒适,实际显示帧率达不到这个值也并无太大影响
// , duration: 2500 //当前视图窗口内最大绘制的波形的持续时间,此处决定了移动速率
// , direction: 1 //波形前进方向,取值:1由左往右,-1由右往左
// , position: 0 //绘制位置,取值-1到1,-1为最底下,0为中间,1为最顶上,小数为百分比
// , centerheight: 1 //中线基础粗细,如果为0不绘制中线,position=±1时应当设为0
// //波形颜色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间
// , linear: [0, "rgba(0,187,17,1)", 0.7, "rgba(255,215,0,1)", 1, "rgba(255,102,0,1)"]
// , centercolor: "" //中线css颜色,留空取波形第一个渐变颜色 });
// }, function (msg, isusernotallow) {
// cleartimeout(t);
// console.log((isusernotallow ? "usernotallow," : "") + "无法录音:" + msg, 1);
// });
})
}
// close
function recstop() {
rec && rec.close() //直接close掉即可,这个例子不需要获得最终的音频文件
realtimesendtry([], 0, true) //最后一次发送
}
return {
recstart: recstart,
recstop: recstop
}
}
vue文件 <template>
<div class="sound">
<textarea v-model="text" name="" id="" cols="30" rows="10" placeholder="点击按钮开始说话,您的语音将会被转为文本,请允许浏览器获取麦克风权限"></textarea>
<div class="btn">
<button @click="startspeechrecognition()">开始识别</button>
<button @click="closewebsocket()">停止识别</button>
</div>
</div>
</template>
<script>
import {getwebsocketurl, renderresult, resetresulttext, tobase64} from "@/assets/speech";
import { jsrecorder } from "@/assets/speech/record.js"; //引入录音文件
export default {
name: "home",
data() {
return {
isrecording: false,
websocket: null,//websocket定义
recstart:null,//开始录音
recstop:null,//结束录音
}
},
methods: {
startspeechrecognition() {
resetresulttext()
// 判断麦克风是否打开
navigator.mediadevices.getusermedia({ audio: true }).then(stream => {
//调用websocket方法
// 如果需要加一些图片验证或者其他验证在这里加一些逻辑,成功之后调用websocket
this.isrecording=true
// 调用录音方法
this.recstart()
this.asrwebsocket()
}).catch(error => {
this.$message.error('麦克风未打开!');
// 麦克风未打开
switch (error.message || error.name) {
case 'permission_denied':
case 'permissiondeniederror':
console.info('用户拒绝提供信息。')
break
case 'not_supported_error':
case 'notsupportederror':
console.info('浏览器不支持硬件设备。')
break
case 'mandatory_unsatisfied_error':
case 'mandatoryunsatisfiederror':
console.info('无法发现指定的硬件设备。')
break
default:
console.info('无法打开麦克风。异常信息:' + (error.code || error.name))
break
}
});
},
asrwebsocket(){
//使用后端提供的地址
let websocketurl = getwebsocketurl()
//new 一个websocket
this.websocket = new websocket(websocketurl)
console.log(this.websocket,'new websocket')
this.websocket.onopen = () => {
console.log('连接成功')
//链接成功之后发送websocket请求参数
this.sendstart()
}
// 客户端接收服务端返回的数据
this.websocket.onmessage = (evt) => {
this.seach = renderresult(evt.data)
console.log("返回识别结果吗",this.seach)
}
this.websocket.onerror = (evt) => {
console.error('websocket-asr错误:', evt)
}
// 关闭连接
this.websocket.onclose = (evt) => {
console.log('websocket-asr关闭:', evt)
}
},
// 请求参数 =====看后端提供的参数信息========
sendstart(){
var params = {
common: {
app_id: "xfyunappid",
},
business: {
language: "zh_cn",
domain: "iat",
accent: "mandarin",
vad_eos: 5000,
dwa: "wpgs",
},
data: {
status: 0,
format: "audio/l16;rate=16000",
encoding: "raw",
},
};
this.websocket.send(json.stringify(params))
},
// 关闭websocket
closewebsocket(){
console.log("关闭socket连接")
//关闭录音
this.recstop();
// websocket关闭参数
if (this.websocket && this.websocket.readystate == 1){
this.websocket.send(
json.stringify({
data: {
status: 2,
format: "audio/l16;rate=16000",
encoding: "raw",
audio: '',
},
})
)
}
if (!this.isrecording) return;
this.isrecording = false;
}
},
mounted() {
this.getimg()
// 使用 jsrecorder 创建一个对象并进行解构操作
const callback = (blob, enctime) => {
// 发送音频arraybuffer
if (this.websocket && this.websocket.readystate == 1 && this.isrecording) {
// 创建一个filereader实例
const reader = new filereader();
// 定义onload事件处理器
reader.onload = function(e) {
// 这里的e.target.result就是一个arraybuffer
const arraybuffer = e.target.result;
// 发送arraybuffer
this.websocket.send(json.stringify({
data: {
status: 1,
format: "audio/l16;rate=16000",
encoding: "raw",
audio: tobase64(new uint8array(arraybuffer)),
},
}));
}.bind(this); // 使用bind确保this指向正确
// 使用filereader的readasarraybuffer方法读取blob
reader.readasarraybuffer(blob);
}
};
const recorder = jsrecorder(callback)
// 开始录音、结束录音方法
this.recstart = recorder.recstart
this.recstop = recorder.recstop
}
}
</script>
发表评论