聲音:這裏的聲音是指經過麥克風會產生一連串的電壓變化,能夠獲得許多[-1,1]之間的數字。若是想要播放,需轉換成pcm格式git
PCM:pcm格式經過三個參數來描述【採樣頻率、採樣位數、聲道數】,從網上找到一張圖:github
輸入採樣頻率:指麥克風收集聲音的頻率,由於麥克風須要將波形的聲音轉換成[-1,1]的信號,用它來指定在單位時間內收集多少個樣本web
輸出採樣頻率:單位時間內播放多少個採樣,通常保持與輸入採樣頻率一致canvas
下面實現一個demo,經過google瀏覽器打開電腦麥克風,利用webrtc相關api錄音,而後轉換成pcm、wav格式,而且用audio標籤進行播放,用cavans畫出音域圖,大體流程以下:api
這裏使用的是 navigator.getUserMedia 方法,固然若是隻是用谷歌瀏覽器,能夠不用兼容處理,主要結構代碼以下瀏覽器
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
navigator.getUserMedia({
audio: true // 這裏面有video 和 audio 兩個參數,視頻選擇video
}, (stream) => {
<!--這個stream 就是採集pcm數據的音源-->
}, (error) => {
console.log(error)
})
複製代碼
下面就用 window.AudioContext進行解析麥克風信息,重點用到createMediaStreamSource、createScriptProcessor、onaudioprocess三個方法,具體結構代碼以下緩存
<!--首先new一個AudioContext對象,做爲聲源的載體 -->
let audioContext = window.AudioContext || window.webkitAudioContext
const context = new audioContext()
<!--將聲音輸入這個對像,stream 就是上面返回音源-->
let audioInput = context.createMediaStreamSource(stream)
<!--建立聲音的緩存節點,createScriptProcessor方法的第二個和第三個參數指的是輸入和輸出都是聲道數,第一個參數緩存大小,通常數值爲1024,2048,4096,這裏選用4096-->
let recorder = context.createScriptProcessor(config.bufferSize, config.channelCount, config.channelCount) // 這裏config是自定義,後面會附帶源碼
<!--此方法音頻緩存,這裏audioData是自定義對象,這個對象會實現wav文件轉換,緩存pcm數據等-->
recorder.onaudioprocess = (e) => {
audioData.input(e.inputBuffer.getChannelData(0))
}
複製代碼
可是在獲取的過程當中要有個觸發點,好比說本實踐的demo最終效果圖以下:bash
audioInput.connect(recorder) //聲音源連接過濾處理器
recorder.connect(context.destination) //過濾處理器連接揚聲器
複製代碼
連接完成後,createScriptProcessor的onaudioprocess方法能夠持續不斷的返回採樣數據,這些數據範圍在[-1,1]之間,類型是Float32。如今要作的就是將它們收集起來,將它轉成pcm文件數據。ide
首先定義個 audioData 對象,用來處理數據,總體結構以下,具體見下面源碼:ui
let audioData = {
size: 0, //錄音文件長度
buffer: [], //錄音緩存
inputSampleRate: context.sampleRate, //輸入採樣率
inputSampleBits: 16, //輸入採樣數位 8, 16
outputSampleRate: config.sampleRate, //輸出採樣率
oututSampleBits: config.sampleBits, //輸出採樣數位 8, 16
input: function(data) { // 實時存儲錄音的數據
},
getRawData: function() { //合併壓縮
},
covertWav: function() { // 轉換成wav文件數據
},
getFullWavData: function() { // 用blob生成文件
},
closeContext: function(){ //關閉AudioContext不然錄音屢次會報錯
},
reshapeWavData: function(sampleBits, offset, iBytes, oData) { // 8位採樣數位
},
getWavBuffer: function() { // 用於繪圖wav格式的buffer數據
},
getPcmBuffer: function() { // pcm buffer 數據
}
}
複製代碼
根據上面的gif圖:
a、第一步點擊錄製會執行章節 一、獲取麥克風權限 和 二、pcm數據獲取 對應流程, 而後onaudioprocess方法中調用audioData對象input方法,用來存儲buffer數據;
b、點擊「下載pcm」標籤,會依次執行audioData對象getRawData、getPcmBuffer方法,可是下載的是txt文件,並不是是pcm文件,因爲不知道如何在js環境將txt文件轉成pcm文件,因此本人在將txt文件下載下來後直接手動修改了拓展名,固然此修改後的文件是能夠播放的,操做流程以下
pcm是沒有頭信息的,只要增長44個字節的頭信息便可轉換成wav,頭信息都是固定的,直接用便可,借用網上千篇一概的代碼片斷
let 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, config.channelCount, true); offset += 2
// 採樣率,每秒樣本數,表示每一個通道的播放速度
data.setUint32(offset, sampleRate, true); offset += 4
// 波形數據傳輸率 (每秒平均字節數) 單聲道×每秒數據位數×每樣本數據位/8
data.setUint32(offset, config.channelCount * sampleRate * (sampleBits / 8), true); offset += 4
// 快數據調整數 採樣一次佔用字節數 單聲道×每樣本的數據位數/8
data.setUint16(offset, config.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
// 寫入採樣數據
data = this.reshapeWavData(sampleBits, offset, bytes, data)
複製代碼
轉成音域圖重點用到AudioContext中的createAnalyser方法,它能夠將音波分解,具體步驟以下:
window.audioBufferSouceNode = context.createBufferSource() //建立聲源對象
audioBufferSouceNode.buffer = buffer /聲源buffer文件流
gainNode = context.createGain() //建立音量控制器
gainNode.gain.value = 2
audioBufferSouceNode.connect(gainNode) //聲源連接音量控制器
let analyser = context.createAnalyser() //建立分析器
analyser.fftSize = 256
gainNode.connect(analyser) //音量控制器連接分析器
analyser.connect(context.destination) //分析器連接揚聲器
複製代碼
而後拿 analyser.frequencyBinCount 數據用canvas進行繪製,主要代碼以下:
let drawing = function() {
let array = new Uint8Array(analyser.frequencyBinCount)
analyser.getByteFrequencyData(array)
ctx.clearRect(0, 0, 600, 200)
for(let i = 0; i < array.length; i++) {
let _height = array[i]
if(!top[i] || (_height > top[i])) {//帽頭落下
top[i] = _height
} else {
top[i] -= 1
}
ctx.fillRect(i * 20, 200 - _height, 4, _height)
ctx.fillRect(i * 20, 200 - top[i] -6.6, 4, 3.3)//繪製帽頭
ctx.fillStyle = gradient
}
requestAnimationFrame(drawing)
}
複製代碼
源碼github地址:audio