實現前端錄音,將音頻blob傳給服務器,然後在服務器端使用百度AI語音識別將結果返回給前端
上一篇文章是將百度AI語音識別Nodejs SDK
版的進行了一遍演示加識別結果返回給前端顯示,這裏是完整的進行前端錄音,然後將壓縮後的音頻對象Blob
傳給服務器,在服務端使用百度AI語音識別,最後將識別結果返回給前端進行顯示。
本篇調用的是第三方庫Recorder.js
,如何調用該庫捕獲HTML5
中的WAV
音頻並將其上傳到服務器或者本地下載,可以查看這篇博客,不過它講解的是上傳到PHP
服務端,這裏我改成了基於Node
搭建的Websocket
服務器。
這是本篇博客的語音識別結果:
百度語音識別
查看文檔知道了我想要的信息,如果想要實現實時語音識別、長時間段的語音、喚醒詞功能、語義解析功能,需要使用Android
、IOS SDK
或者Linux C++ SDK
版本,而我使用的Nodejs SDK
是不支持的。
1、規格參數要求
-
語音時長上線爲60s,超出講返回錯誤
-
原始錄音文件爲
pcm
、wav
或者amr
格式,不區分大小寫,推薦使用pcm
-
錄音採樣率爲16000,聲道爲單通道
-
支持普通話、英文、粵語、四川話
-
項目結構
調用百度AI平臺語音識別的
Nodejs SDK
,查看文檔快速入門,可以查看如何調用。首先將nodejs-sdk下載下來,下載後將目錄裏的
speech
文件夾拷貝到你的項目文件夾中,其中assets
是存放錄音音頻的地方,然後進入node
文件夾下的位置進行安裝依賴包:npm install
我的項目文件夾目錄如下:
audio_asr_baidu ├─ package-lock.json └─ speech ├─ .gitignore ├─ assets │ ├─ 16k_test.pcm │ └─ recorder.wav ├─ cpp │ ├─ .gitignore │ ├─ README.md │ ├─ build.sh │ └─ main.cpp └─ node ├─ .gitignore ├─ README.md ├─ index.html ├─ main.js ├─ node_modules ├─ package-lock.json ├─ package.json └─ style.css
然後在
node
文件夾裏的index.html
是我的客戶端文件,main.js
是我的服務端文件。
搭建Websocket
服務器
在main.js
文件裏搭建websocket
服務器,首先安裝相關依賴模塊:
npm i ws -S
然後搭建:
let Server = require('ws').Server;
const wss = new Server({
port: 9001
})
// 連接服務器
wss.on('connection', ws => {
console.log('server connected');
})
ws.on('error', error => {
console.log('Error:' + error);
})
ws.on('close', () => {
console.log('Websocket is closed');
})
})
// 斷開連接
wss.on('disconnection', ws => {
ws.on('message', msg => {
console.log('server recived msg:' + msg);
})
})
然後在index.html
中:
let ws = new WebSocket('ws://localhost:9001');
ws.onopen = e => {
console.log('Connection to server opened');
}
啓動服務:
node main.js
就可以在控制檯看見這樣的打印信息:
// 客戶端的打印信息:
Connection to server opened
// 服務端的打印信息:
server connected
前端錄音
客戶端實現錄音之後,將壓縮後的音頻對象Blob
傳給服務器:
<!DOCTYPE html>
<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">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="controls">
<button id="recordButton">Record</button>
<button id="stopButton" disabled>Stop</button>
</div>
<p id="out-txt">You said:</p>
<h3>Recordings</h3>
<ol id="recordingsList"></ol>
<script src="https://cdn.rawgit.com/mattdiamond/Recorderjs/08e7abd9/dist/recorder.js"></script>
//連接服務器
let ws = new WebSocket('ws://localhost:9001');
ws.onopen = e => {
console.log('Connection to server opened');
}
URL = window.URL || window.webkitURL;
var gumStream; //stream from getUserMedia()
var rec; //Recorder.js object
var input; //MediaStreamAudioSourceNode
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext
var recordButton = document.getElementById("recordButton");
var stopButton = document.getElementById("stopButton");
recordButton.addEventListener("click", startRecording);
stopButton.addEventListener("click", stopRecording);
// 錄音
function startRecording() {
console.log("recordButton clicked");
var constraints = {
audio: true,
video: false
}
recordButton.disabled = true;
stopButton.disabled = false;
// 獲取錄音權限 然後開始錄音
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
console.log("getUserMedia() success, stream created, initializing Recorder.js ...");
audioContext = new AudioContext();
gumStream = stream;
input = audioContext.createMediaStreamSource(stream);
rec = new Recorder(input, {
numChannels: 1 // 單聲道
})
//開始錄音
rec.record()
console.log("Recording started");
}).catch(function(err) {
recordButton.disabled = false;
stopButton.disabled = true;
});
}
// 停止錄音
function stopRecording() {
console.log("stopButton clicked");
stopButton.disabled = true;
recordButton.disabled = false;
rec.stop();
gumStream.getAudioTracks()[0].stop();
// 創建一個blob對象讓它以wav格式下載
rec.exportWAV(createDownloadLink);
}
// 接收服務端發的消息
ws.onmessage = e => {
console.log(e.data);
setTimeout(() => {
document.getElementById("out-txt").innerHTML += e.data
}, 3000);
}
// 創建下載鏈接
function createDownloadLink(blob) {
console.log(blob);
ws.send(blob);
var url = URL.createObjectURL(blob);
var au = document.createElement('audio');
var li = document.createElement('li');
var link = document.createElement('a');
var filename = new Date().toISOString();
au.controls = true;
au.src = url;
link.href = url;
link.download = filename + ".wav";
link.innerHTML = "Save to disk";
li.appendChild(au);
li.appendChild(document.createTextNode(filename + ".wav "))
li.appendChild(link);
}
這樣,在該頁面會創建下載連接,並以錄音日期爲文件名,可以選擇下載,同時也會將音頻對象傳到服務器。
語音識別
因爲前端通過音頻流文件上傳到後臺後,不再是保存爲wav
格式的音頻,而是處理流的形式轉爲二進制數組,直接調用百度語音識別SDK
方法,即可返回識別結果,不必編碼後發給後端,後端然後再解碼。
let AipSpeech = require("baidu-aip-sdk").speech;
let Server = require('ws').Server;
const wss = new Server({
port: 9001
})
let resTxt;
wss.on('connection', ws => {
console.log('server connected');
ws.on('message', data => {
console.log('server recived audio blob');
// 務必替換百度雲控制檯中新建百度語音應用的 Api Key 和 Secret Key
let client = new AipSpeech(0, 'Api Key', 'Secret Key');
let voiceBase64 = new Buffer(data);
client.recognize(voiceBase64, 'wav', 16000).then(function(result) {
console.log('語音識別本地音頻文件結果: ' + JSON.stringify(result));
resTxt = JSON.parse(JSON.stringify(result));
}, function(err) {
console.log(err);
});
})
// 將結果傳給前端
ws.send(resTxt);
ws.on('error', error => {
console.log('Error:' + error);
})
ws.on('close', () => {
console.log('Websocket is closed');
})
})
wss.on('disconnection', ws => {
ws.on('message', msg => {
console.log('server recived msg:' + msg);
})
})
這是前端說話錄音傳給後臺語音識別的結果,將結果使用websocket
傳給前端,顯示在標籤內就可以了: