通过科大讯飞实时转写接口了解音频数据采集
音频采集相关术语
采样率(sample rate)
音频采样率是指录音设备在一秒钟内对声音信号的采样次数,采样频率越高声音的还原就越真实越自然
采样位数(sample size)
即采样值或取样值(就是将采样样本幅度量化)。它是用来衡量声音波动变化的一个参数,也可以说是声卡的分辨率。它的数值越大,分辨率也就越高,所发出声音的能力越强。
每个采样数据记录的是振幅, 采样精度取决于采样位数的大小:
1 字节(也就是8bit) 只能记录 256 个数, 也就是只能将振幅划分成 256 个等级;
2 字节(也就是16bit) 可以细到 65536 个数, 这已是 CD 标准了;
4 字节(也就是32bit) 能把振幅细分到 4294967296 个等级, 实在是没必要了.
语音编码(format)
语音编码指语音数据存储和传输的方式。请注意,语音编码和语音文件格式不同。例如常见的.WAV文件格式,会在其头部定义语音数据的具体编码,其中的音频数据通常是使用PCM编码,但也有可能是AMR或其他编码。
声道(sound chanel)
声道是指声音在录制时在不同空间位置采集的相互独立的音频信号,所以声道数也就是声音录制时的音源数量。常见的音频数据为单声道或双声道(立体声)。
除录音文件识别以外的服务只支持单声道(mono)语音数据,如果您的数据是双声道或其他,需要先转换为单声道才能识别。
通过科大讯飞实时转写可以得到音频输入流的必要条件:
1音频属性:采样率16k、位长16bits、单声道。
2音频格式:pcm
3 数据发送:建议音频流每40ms发送1280字节
4语言种类:中文普通话。
将以上4点准备好数据发送至服务接口即可。
音频数据采集逻辑
1请求麦克风资源
2创建一个音频环境对象
3读取音频流,将音频流放入储存声音缓存区中
4根据采集音频数据标准,进行格式化与压缩
将压缩后的音频数据传递到科大讯飞接口
开始使用AudioContext
AudioContext是一个专门用于音频处理的接口,并且工作原理是将AudioContext创建出来的各种节点(AudioNode)相互连接,音频数据流经这些节点并作出相应处理。
在HTML5通过AudioContext对象可以实现音频的数据采集
由于浏览器兼容性问题,我们需要为不同浏览器配置AudioContext,在这里我们可以用下面这个表达式来统一对AudioContext的访问。
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext(); //实例化AudioContext对象
浏览器 | chrome | Firefox | IE | Opera | safari |
支持版本 | 10.0 | 25.0 | 不支持 | 25.0 | 支持版本 |
AudioContext.createMediaStreamSource() //创建一个MediaStreamAudioSourceNode接口来关联可能来自本地计算机麦克风或其他来源的音频流MediaStream.
AudioContext.createScriptProcessor() //创建一个可以通过JavaScript直接处理音频的ScriptProcessorNode
//音频采集
recorder.onaudioprocess = function (e) {
var sizeData= audioData.input(e.inputBuffer.getChannelData(0)); //将采集的音频数据放入缓冲区,返回音频大小
if(sizeData < 150000){
// console.log("测试音频数据" + sizeData);
}else {
// this.reocrder.transforTest('http://localhost:8080/return/updateforwarfile');
var url = base + "/evnetseventreceivelow/evn-ets-eventreceive-low!updateForAudioStream.do"
var fd = new FormData();
var buffer = audioData.getPureWavData(0);
fd.set('wavData', buffer);
fd.set('sign', "open");
console.log("wavSize: " + buffer.size);
// document.getElementById('btn-text-content').value = "当前录音长度为:" + buffer.size;
//方法二
$.ajax({
type:"post",
url:url,
data:fd,
contentType: false,
processData: false,
dataType:"json",
success:function(res){
if(res != 'undefined'){
document.getElementById('voiceWrite').value = res.message;
}
},
error:function (msg) {
}
})
}
};
将超过150000大小的音频数据,进行处理放入后台,同时初始化缓冲区。(以上的方法是通过ajax轮询进行的查询,这种方式,存在“有延迟”,“部分音频数据在处理过程中容易造成音频数据丢失结果”)。
通过WebSocket传递音频数据
(一):实现websocket连接
var websocketOfXunFei;
var sumStr = ''; //总
var temporaryStr = ''; //临时
var protocol = window.location.protocol;
var baseService = window.location.host;
var pathName = window.location.pathname;
var projectName = pathName.substring(0,pathName.substr(1).indexOf('/')+1);
var protocolStr = document.location.protocol;
var baseHttpProtocol = "ws://";
var svrUrl = baseHttpProtocol + baseService + projectName;
function init() {
output = document.getElementById("output");
}
function send_messageOfKeDaXunFei() {
sumStr = ''; //总
if ('WebSocket' in window) {
websocketOfXunFei = new WebSocket(svrUrl+ "/websocket/streamOfXunfei");
} else if ('MozWebSocket' in window) {
websocketOfXunFei = new MozWebSocket(svrUrl+ "/websocket/streamOfXunfei");
} else {
// websocket = new SockJS("http://localhost:8080/amp/sockjs/webSocketServer");
}
// websocket = new WebSocket(wsUri);
websocketOfXunFei.onopen = function(evt) {
onOpenOfXunFei(evt)
};
websocketOfXunFei.onmessage = function(evt) {
onMessageOfXunFei(evt)
};
websocketOfXunFei.onerror = function(evt) {
onErrorOfXunFei(evt)
};
// websocket.onclose = function(){
//
// }
}
function onCloseVoice_ofKeDaXunfei(e){
console.log(e);
websocketOfXunFei.close(); //暂时注释
}
function onOpenOfXunFei(evt) {
}
function onMessageOfXunFei(evt) {
console.info(evt.data)
var jsonObj = jQuery.parseJSON(evt.data);
if("1" == jsonObj.nameStr){
temporaryStr = jsonObj.messageStr;
}else if("0" == jsonObj.nameStr){
sumStr+= jsonObj.messageStr;
temporaryStr = "";
}else if("TranscriptionResultChanged" == jsonObj.nameStr){
temporaryStr = jsonObj.messageStr;
}else if("SentenceEnd" == jsonObj.nameStr){
sumStr+= jsonObj.messageStr;
temporaryStr = "";
}
writeToScreen(sumStr + temporaryStr);
}
function onErrorOfXunFei(evt) {
writeToScreen('ERROR:' + evt.data);
}
function doSend(message) {
websocketOfXunFei.send(message); //测试已注释
}
function writeToScreen(message) {
document.getElementById('voiceWriteStr').value = message;
}
window.addEventListener("load", init, false);
(二):通过websocket发送音频数据
//音频采集
recorder.onaudioprocess = function (e) {
var sizeData= audioData.input(e.inputBuffer.getChannelData(0));
var buffer = audioData.getPureWavData(0); //经过压缩后的数据
doSend(buffer); //通过websocke中的方法进行发送
};
(三):websocket接受前端发送音频二进制数据,调用科大讯飞发送音频数据接口
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(byte[] message, Session session) throws InterruptedException {
// System.out.println("收到来自前端websocket"+sid+"的信息:"+message.length);
//调用科大讯飞接口static
EvnEtsEventreceiveLowAction.sendVoiceData(client,message); //这里暂时这样写
}
将科大讯飞返回的文字,发送到前端
public static void sendInfoOfTest(String name, String message,String sid) throws IOException {
System.out.println("推送消息到窗口"+sid+",推送内容:"+message);
for (WebSocketServerOfXunFei item : webSocketSet) {
try {
//这里可以设定只推送给这个sid的,为null则全部推送
JSONObject jsonObj = new JSONObject();
jsonObj.put("nameStr", name);
jsonObj.put("messageStr", message);
if(sid==null) {
item.sendMessage(jsonObj.toString());
}else if(item.sid.equals(sid)){
item.sendMessage(jsonObj.toString());
}
} catch (IOException e) {
continue;
}
}
}
以上通过websocket实现了音频与返回数据(文字)中转能力,websocket为实时通讯,对比轮询返回的数据准确率高,实时展示效果良好。