JS实时麦克风录音并通过WebSocket将pcm传到后台并处理
前端
<html>
<head>
<meta charset="UTF-8">
<title>Simple Recorder.js demo with record, stop and pause</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 控制宽度的自动适应 -->
<style type="text/css">
.comments {
width: 100%;
overflow: auto;
word-break: break-all;
}
</style>
</head>
<body>
<div id="controls">
<button id="recordButton">Record</button>
<button id="stopButton">Stop</button>
</div>
<textarea id="textResult" class="comments" rows="10" cols="10"></textarea>
</body>
<script type="text/javascript" src="./js/recorder3.js"></script>
<script>
var ws = null;
var interval;
let recorder = new Recorder({
sampleBits: 16,
sampleRate: 16000,
numChannels: 1,
compiling: true
});
var recordButton = document.getElementById("recordButton");
var stopButton = document.getElementById("stopButton");
var textResult = document.getElementById("textResult");
recordButton.addEventListener("click", startRecording);
stopButton.addEventListener("click", stopRecording);
function startRecording() {
console.log("recordButton clicked");
recorder.start().then(() => {
useWebSocket();
}, (error) => {
console.log(`出错了`);
});
}
function stopRecording() {
console.log("stopButton clicked", recorder.getPCMBlob());
recorder.stop();
if (ws) {
ws.close();
}
clearInterval(interval);
textResult.innerText = '';
}
function useWebSocket() {
ws = new WebSocket("ws://" + window.location.host + "/websocket/chat/audio");
ws.binaryType = 'arraybuffer';
ws.onopen = function () {
console.log('握手成功');
if (ws.readyState === 1) {
interval = setInterval(() => {
ws.send(recorder.getNextData());
}, 1000)
}
};
ws.onmessage = function (msg) {
var jsonStr = msg.data;
var json = JSON.parse(jsonStr);
textResult.innerText = json.msg;
autoTextarea(document.getElementById("textResult"));
};
ws.onerror = function (err) {
console.error(err);
textResult.innerText = '';
};
ws.onclose = function (msg) {
console.info(msg);
textResult.innerText = '';
};
}
var autoTextarea = function (elem, extra, maxHeight) {
if (elem.length > 0) {
for (var i = 0; i < elem.length; i++) {
e(elem[i]);
}
} else {
e(elem);
}
function e(elem) {
extra = extra || 0;
var isFirefox = !!document.getBoxObjectFor || 'mozInnerScreenX' in window,
isOpera = !!window.opera && !!window.opera.toString().indexOf('Opera'),
addEvent = function (type, callback) {
elem.addEventListener ?
elem.addEventListener(type, callback, false) :
elem.attachEvent('on' + type, callback);
},
getStyle = elem.currentStyle ? function (name) {
var val = elem.currentStyle[name];
if (name === 'height' && val.search(/px/i) !== 1) {
var rect = elem.getBoundingClientRect();
return rect.bottom - rect.top -
parseFloat(getStyle('paddingTop')) -
parseFloat(getStyle('paddingBottom')) + 'px';
}
;
return val;
} : function (name) {
return getComputedStyle(elem, null)[name];
},
minHeight = parseFloat(getStyle('height'));
elem.style.resize = 'none';
var change = function () {
var scrollTop, height,
padding = 0,
style = elem.style;
if (elem._length === elem.value.length) return;
elem._length = elem.value.length;
if (!isFirefox && !isOpera) {
padding = parseInt(getStyle('paddingTop')) + parseInt(getStyle('paddingBottom'));
}
;
scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
elem.style.height = minHeight + 'px';
if (elem.scrollHeight > minHeight) {
if (maxHeight && elem.scrollHeight > maxHeight) {
height = maxHeight - padding;
style.overflowY = 'auto';
} else {
height = elem.scrollHeight - padding;
style.overflowY = 'hidden';
}
;
style.height = height + extra + 'px';
scrollTop += parseInt(style.height) - elem.currHeight;
document.body.scrollTop = scrollTop;
document.documentElement.scrollTop = scrollTop;
elem.currHeight = parseInt(style.height);
}
;
};
addEvent('propertychange', change);
addEvent('input', change);
addEvent('focus', change);
change();
}
};
</script>
</html>
后端websoket接收
@Override
@OnMessage(maxMessageSize = 10000000)
public void onMessage(ByteBuffer message) {
if (ObjectUtil.isEmpty(message)) {
return;
}
CompletableFuture.runAsync(() -> {
String userName = this.getUserName();
appendBuffer(message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = message.array();
try {
byteArrayOutputStream.write(bytes);
} catch (IOException e) {
try {
byteArrayOutputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
e.printStackTrace();
return;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PcmCovWavUtil.convertWaveFile(byteArrayOutputStream, outputStream);
wav waveFile = new wav();
waveFile.GetFromBytes(outputStream.toByteArray());
try {
ByteArrayOutputStream stream = byteArrayOutputStreamConcurrentHashMap.get(userName);
if (ObjectUtil.isNotEmpty(stream)) {
String fileName = "d://temp_" + userName + ".wav";
FileOutputStream fileOutputStream = new FileOutputStream(new File(fileName));
IoUtil.copy(new ByteArrayInputStream(stream.toByteArray()), fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
String result = ApiUtils.sendToServer("qwertasd", waveFile.fs, waveFile.samples);
StringBuffer stringBuffer = appendResult(result);
Message msg = new Message(getUserName(), Message.MsgConstant.MSG_TO_ALL, stringBuffer.toString());
super.onMessage(msg.toString());
}, threadPoolExecutor);
}
主要思路
- 前端链接后端websoket服务
- 前端实时将pcm音频流传输到后端
- 后端定义静态变量,合并接收pcm片段
- 将合并的pcm流任意转换为wav、mp3等
完整项目参见:https://gitee.com/yzdyzdyzd/speechToText
附录
- 正确pcm 格式
|