Vue 录音实时上传,分析问题,解决问题,最后展示完整方案
Posted 香菜聊游戏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue 录音实时上传,分析问题,解决问题,最后展示完整方案相关的知识,希望对你有一定的参考价值。
因为公司事做语音翻译的,在官网上有个试用的功能,需要在网页上录音,并传到后台进行翻译并进行语音合成。
业务需求就是在网页上使用录音功能,每100ms 通信一次,将数据传输到后端进行一套链路处理,
原来的业务逻辑是使用http 进行通信,每1s 通信一次,但是想优化下准备使用websocket 通信,降低翻译延迟,将延时降低为100ms,所以进行优化。
1、项目问题
项目使用的前端框架是Vue,但是因为我是后端开发,所以我对vue不懂,前端的开发使用的插件是recorderX 。在每秒发送数据的时候还是正常的,但是在将定时器调整为100ms的时候,数据总是不能正常发送,要么就是空包,要么就是一下数据量很大,超过了后端的长度上限,导致无法处理,业务上也不能满足需求,因为前端还有其他事情需要处理,在研究了几天之后,果断放弃了,一直把这个问题闲置下来
可以看到前面的只有44个字节,后面连续几帧数据一直没变,最后突然变大。
2、解决问题的路径
因为我最近项目上不是很忙,所以有一些时间,这个录音的问题就暂时放到我这了,有问题就要解决,下面说一下我的解决问题的路径。
1.学习vue语法
前端我是不太懂,只在上古时代的时候写过jsp 和 js ,Vue 这种高级框架我哪里懂,所以得学习下。不得不说前端的东西现在进化的挺好,虽然vue 依然不怎么懂,但是大概知道vue的模块分三部分
第一部分是界面的配置
第二部分是逻辑代码
第三部分是网页样式
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default
name: 'App',
components:
HelloWorld
</script>
<style>
#app
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
</style>
2.搭建环境
vue的环境搭建还蛮复杂的,我选择的ide 是 webstorm ,但是据说前端使用的是VScode,作为idea 的重度用户,果断选择了webstorm.
安装nodejs,
部署vue 环境
环境大概搭建完成之后,现在依然不能理解的是为什么每次都要使用命令启动,npm run serve ,不过不影响我解决问题
3.代码问题
前端遇到的问题是在定时器缩短到100ms 的时候,recorder 在每次获取数据的时候无法正常的获取数据,在定时器的时候稍微长点的时候是正常的
基于这种问题我有几个猜测
第一:vue 的刷新机制导致recorder 的数据无法技术放入buffer 中,(应该不是)
第二:没有正确的使用recorderX,在获取的时候没有清楚buffer( 在读取recorderX 的源码之后并没有发现什么异常,试着调用了recorder.clear(),然后并没有什么卵用)
第三:写法有问题 (在尝试了几种写法后果断放弃)
第四:插件有问题,换插件(在绝望之际,我放弃了,直接换了插件,最后完美解决问题)
3、代码解决
最后的解决办法是通过换插件,将原来的插件换掉,换成HZRecorder
下面是源码,我做了一些优化。文件名为HZRecorder.js
export function HZRecorder(stream, config)
config = config || ;
config.sampleBits = config.sampleBits || 16; //采样数位 8, 16
config.sampleRate = config.sampleRate || 16000; //采样率16khz
var context = new (window.webkitAudioContext || window.AudioContext)();
var audioInput = context.createMediaStreamSource(stream);
var createScript = context.createScriptProcessor || context.createjavascriptNode;
var recorder = createScript.apply(context, [4096, 1, 1]);
var audioData =
size: 0 //录音文件长度
, buffer: [] //录音缓存
, inputSampleRate: context.sampleRate //输入采样率
, inputSampleBits: 16 //输入采样数位 8, 16
, outputSampleRate: config.sampleRate //输出采样率
, outputSampleBits: config.sampleBits //输出采样数位 8, 16
, input: function (data)
this.buffer.push(new Float32Array(data));
this.size += data.length;
, compress: function (clearBuff) //合并压缩
//合并
var data = new Float32Array(this.size);
var offset = 0;
for (var i = 0; i < this.buffer.length; i++)
data.set(this.buffer[i], offset);
offset += this.buffer[i].length;
//压缩
var compression = parseInt(this.inputSampleRate / this.outputSampleRate);
var length = data.length / compression;
var result = new Float32Array(length);
var index = 0, j = 0;
while (index < length)
result[index] = data[j];
j += compression;
index++;
// TODO 每次获取清空缓冲区
if (clearBuff)
this.buffer = []
this.size = 0
return result;
, encodeWAV: function (clearBuff)
var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);
var sampleBits = Math.min(this.inputSampleBits, this.outputSampleBits);
var bytes = this.compress(clearBuff);
var dataLength = bytes.length * (sampleBits / 8);
var buffer = new ArrayBuffer(44 + dataLength);
var data = new DataView(buffer);
var channelCount = 1;//单声道
var offset = 0;
var writeString = function (str)
for (var i = 0; i < str.length; i++)
data.setUint8(offset + i, str.charCodeAt(i));
// 资源交换文件标识符
writeString('RIFF'); offset += 4;
// 下个地址开始到文件尾总字节数,即文件大小-8
data.setUint32(offset, 36 + dataLength, true); offset += 4;
// WAV文件标志
writeString('WAVE'); offset += 4;
// 波形格式标志
writeString('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 += 4;
// 采样数据总数,即数据总大小-44
data.setUint32(offset, dataLength, true); offset += 4;
// 写入采样数据
if (sampleBits === 8)
for (var i = 0; i < bytes.length; i++, offset++)
var s = Math.max(-1, Math.min(1, bytes[i]));
var val = s < 0 ? s * 0x8000 : s * 0x7FFF;
val = parseInt(255 / (65535 / (val + 32768)));
data.setInt8(offset, val, true);
else
for (var i2 = 0; i2 < bytes.length; i2++, offset += 2)
var s2 = Math.max(-1, Math.min(1, bytes[i2]));
data.setInt16(offset, s2 < 0 ? s2 * 0x8000 : s2 * 0x7FFF, true);
return new Blob([data], type: 'audio/wav' );
;
//开始录音
this.start = function ()
audioInput.connect(recorder);
recorder.connect(context.destination);
//停止
this.stop = function ()
recorder.disconnect();
//获取音频文件
this.getBlob = function (clearBuff)
clearBuff = clearBuff || false;
// this.stop();
return audioData.encodeWAV(clearBuff);
//回放
this.play = function (audio)
// var blob=this.getBlob();
// saveAs(blob, "F:/3.wav");
audio.src = window.URL.createObjectURL(this.getBlob());
//上传
this.upload = function ()
return this.getBlob()
//音频采集
recorder.onaudioprocess = function (e)
audioData.input(e.inputBuffer.getChannelData(0));
//record(e.inputBuffer.getChannelData(0));
调用的地方:文件名为HelloWorld.vue
<template>
<div>
<button @click="btnClick">开始</button>
</div>
</template>
<script>
import HZRecorder from '../HZRecorder.js';
export default
name: 'HelloWorld',
props:
msg: String
,
data()
return
index:0,
timeCount:0,
recorder :HZRecorder,
audio_context:AudioContext,
;
,
methods:
btnClick: function ()
this.recorder.start()
setInterval(() =>
var blob = this.recorder.getBlob();
const file = new File([blob], 'audio.wav');
console.log("-----------"+ file.size)
var blob2 = blob.slice(this.index,blob.size -1);
console.log("----blob2-------" + blob2.size)
this.index = blob.size -1;
// this.recorder.start()
this.timeCount = this.timeCount +1
if (this.timeCount === 50)
const blobUrl = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.style.display = 'none'
a.download = new Date().getTime() +'.wav'
a.href = blobUrl
a.click()
, 100)
,
mounted()
var that = this
this.$nextTick(() =>
try
// <!-- 检查是否能够调用麦克风 -->
window.AudioContext = window.AudioContext || window.webkitAudioContext;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
window.URL = window.URL || window.webkitURL;
that.audio_context = new AudioContext;
console.log('navigator.getUserMedia ' + (navigator.getUserMedia ? 'available.' : 'not present!'));
catch (e)
alert('No web audio support in this browser!');
navigator.getUserMedia(audio: true, function (stream)
that.recorder = new HZRecorder(stream,
sampleBits: 16,
sampleRate: 8000
)
console.log('初始化完成');
, function(e)
console.log('No live audio input: ' + e);
);
)
,
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3
margin: 40px 0 0;
ul
list-style-type: none;
padding: 0;
li
display: inline-block;
margin: 0 10px;
a
color: #42b983;
</style>
在页面上点击开始,
然后F12 打开控制台看到输出,每帧的长度是1681,完成了需求
总结
做了后端蛮久的突然接触前端,虽然想学但是总感觉有点难,心理上有点怂,虽然最后做了各种猜测,也算解决了问题,但是还是需要对学习陌生领域的时候的心理状态做一个复盘,要清空自己,不能用原来的历史知识排斥新接触的知识,先吸收后理解
本书从Vue.js框架技术的基础概念出发,逐步深入Vue.js进阶实战,并在最后配合一个网站项目和一个后台系统开发实战案例,重点介绍了使用Vue.js+axios+ElementUI+wangEditor进行前端开发和使用组件进行Vue单页面网页复用,让读者不但可以系统地学习Vue.js前端开发框架的相关知识,而且还能对业务逻辑的分析思路、实际应用开发有更为深入的理解。
本书语言平实,用词诙谐,案例丰富,实用性强,特别适合刚入社会的职场新人、Vue.js框架的初级读者和进阶读者阅读,也适合希望从后台开发转型做前端的程序员等其他编程爱好者阅读。另外,本书也适合作为相关培训机构的教材使用。
京东自营购买链接
《Vue.js框架与Web前端开发从入门到精通》(舒志强)【摘要 书评 试读】- 京东图书
当当自营购买链接
以上是关于Vue 录音实时上传,分析问题,解决问题,最后展示完整方案的主要内容,如果未能解决你的问题,请参考以下文章