海康SDK/Ehome協議/RTSP協議/GB28181安防視頻雲服務EasyCVR能夠通過GB28181協議進行級聯,假如攝像頭或設備支持音頻的話,EasyCVR同樣也能夠進行音頻采集。
EasyCVR視頻平台前端js 使用webapi采集設備音頻,需特別注意getUserMedia在非localhost和127的情況下,需要開啟https。
前端基本步驟
1、利用webrtc的getUserMedia方法獲取設備音頻輸入,使用audioprocess得到音頻流(pcm流,范圍-1到1)。
2、重采樣,前端采樣率為48000,后端需要的采樣率為8000 ,所有需要合並壓縮
3、值轉換,每個chunk中獲取到的輸入數據是一個長度為4096的Float32Array定型數組,也就是說每個采樣點信息是用32位浮點來存儲的。
32位存儲的采樣幀數值,是用-1到1來映射16bit存儲范圍-32768~32767的。
如下為轉換代碼:
function floatTo16BitPCM(output, offset, input) { for (let i = 0; i < input.length; i++, offset += 2) { //下面一行代碼保證了采樣幀的值在-1到1之間,因為有可能在多聲道合並或其他狀況下超出范圍 let s = Math.max(-1, Math.min(1, input[i])); //將32位浮點映射為16位整形表示的值 output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); } }
說明:
如果s>0其實就是將01映射到到032767,正數第一位符號位為0,所以32767對應的就是0111 1111 1111 1111也就是0x7FFF,直接把s當系數相乘就可以了;當s為負數時,需要將0-1映射到0-32768,所以s的值也可以直接當做比例系數來進行轉換計算,負數在內存中存儲時需要使用補碼,補碼是原碼除符號位以外按位取反再+1得到的,所以-32768原碼是1000 0000 0000 0000(溢出的位直接丟棄),除符號位外按位取反得到1111 1111 1111 1111,最后再+1運算得到1000 0000 0000 0000(溢出的位也直接丟棄),用16進制表示就是0x8000。順便多說一句,補碼的存在是為了讓正值和負值在二進制形態上相加等於0。
4、websocket 建立客戶端鏈接發送數據,觀察發現采集回調回80ms左右觸發一次,在觸發的回調函數中發送數據
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta name="apple-mobile-web-capable" content="yes"> <title>錄音並傳遞給后台</title> </head> <body> <button id="intercomBegin">開始對講</button> <button id="intercomEnd">關閉對講</button> </body> <script type="text/javascript"> var begin = document.getElementById('intercomBegin'); var end = document.getElementById('intercomEnd'); var ws = null; //實現WebSocket var record = null; //多媒體對象,用來處理音頻 function init(rec) { record = rec; } //錄音對象 var Recorder = function(stream) { var sampleBits = 16; //輸出采樣數位 8, 16 var sampleRate = 8000; //輸出采樣率 var context = new AudioContext(); var audioInput = context.createMediaStreamSource(stream); var recorder = context.createScriptProcessor(4096, 1, 1); var audioData = { size: 0, //錄音文件長度 buffer: [], //錄音緩存 inputSampleRate: 48000, //輸入采樣率 inputSampleBits: 16, //輸入采樣數位 8, 16 outputSampleRate: sampleRate, //輸出采樣數位 oututSampleBits: sampleBits, //輸出采樣率 clear: function() { this.buffer = []; this.size = 0; }, input: function(data) { this.buffer.push(new Float32Array(data)); this.size += data.length; }, compress: function() { //合並壓縮 //合並 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++; } return result; }, encodePCM: function() { //這里不對采集到的數據進行其他格式處理,如有需要均交給服務器端處理。 var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate); var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits); var bytes = this.compress(); var dataLength = bytes.length * (sampleBits / 8); var buffer = new ArrayBuffer(dataLength); var data = new DataView(buffer); var offset = 0; for (var i = 0; i < bytes.length; i++, offset += 2) { var s = Math.max(-1, Math.min(1, bytes[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); } return new Blob([data]); } }; var sendData = function() { //對以獲取的數據進行處理(分包) var reader = new FileReader(); reader.onload = e => { var outbuffer = e.target.result; var arr = new Int8Array(outbuffer); if (arr.length > 0) { var tmparr = new Int8Array(1024); var j = 0; for (var i = 0; i < arr.byteLength; i++) { tmparr[j++] = arr[i]; if (((i + 1) % 1024) == 0) { ws.send(tmparr); if (arr.byteLength - i - 1 >= 1024) { tmparr = new Int8Array(1024); } else { tmparr = new Int8Array(arr.byteLength - i - 1); } j = 0; } if ((i + 1 == arr.byteLength) && ((i + 1) % 1024) != 0) { ws.send(tmparr); } } } }; reader.readAsArrayBuffer(audioData.encodePCM()); audioData.clear();//每次發送完成則清理掉舊數據 }; this.start = function() { audioInput.connect(recorder); recorder.connect(context.destination); } this.stop = function() { recorder.disconnect(); } this.getBlob = function() { return audioData.encodePCM(); } this.clear = function() { audioData.clear(); } recorder.onaudioprocess = function(e) { var inputBuffer = e.inputBuffer.getChannelData(0); audioData.input(inputBuffer); sendData(); } } /* * WebSocket */ function useWebSocket() { ws = new WebSocket("ws://192.168.2.9:8080/websocket"); ws.binaryType = 'arraybuffer'; //傳輸的是 ArrayBuffer 類型的數據 ws.onopen = function() { console.log('握手成功'); if (ws.readyState == 1) { //ws進入連接狀態,則每隔500毫秒發送一包數據 record.start(); } }; ws.onmessage = function(msg) { console.info(msg) } ws.onerror = function(err) { console.info(err) } } /* * 開始對講 */ begin.onclick = function() { navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia; if (!navigator.getUserMedia) { alert('瀏覽器不支持音頻輸入'); } else { navigator.getUserMedia({ audio: true }, function(mediaStream) { init(new Recorder(mediaStream)); console.log('開始對講'); useWebSocket(); }, function(error) { console.log(error); switch (error.message || error.name) { case 'PERMISSION_DENIED': case 'PermissionDeniedError': console.info('用戶拒絕提供信息。'); break; case 'NOT_SUPPORTED_ERROR': case 'NotSupportedError': console.info('瀏覽器不支持硬件設備。'); break; case 'MANDATORY_UNSATISFIED_ERROR': case 'MandatoryUnsatisfiedError': console.info('無法發現指定的硬件設備。'); break; default: console.info('無法打開麥克風。異常信息:' + (error.code || error.name)); break; } } ) } } /* * 關閉對講 */ end.onclick = function() { if (ws) { ws.close(); record.stop(); console.log('關閉對講以及WebSocket'); } } </script> </html>
更多關於EasyCVR視頻平台
EasyCVR安防視頻雲服務的主要功能是將本地局域網內連通的RTSP視頻源,包括但不限於數字網絡攝像機、DVR、NVR、編碼器等設備視頻流,通過RTMP協議推送到阿里、騰訊等公有雲廠商的視頻服務中,具備優秀的視頻轉碼、播放、級聯能力。同時該新系統也支持海康SDK、Ehome協議,GB28181國標協議,是一套真正的視頻融合平台。
EasyCVR已經支持集成海康EHome協議,感興趣的用戶可以閱讀一下《EasyCVR集成海康EHome協議系列——配置及協議介紹》、《EasyCVR集成海康EHome協議系列——Ehome協議調用流程介紹》等文。