在分享之前先貼上借鑒的大佬們的博客,感謝這些巨人,,關(guān)于錄音:https://blog.csdn.net/sweetsuzyhyf/article/details/50469881 廢話:想不到我的第一篇博客是關(guān)于前端,作為一名后端的小菜,,前端方面肯定還有很多不足之處,,如果文章有任何問題歡迎指正。感謝大家,。好了,!廢話不多說下面講一下需求。
需求:公司要求實(shí)現(xiàn)web端的錄音并通過websocket實(shí)時上傳至java后臺,,而且能通過vlc實(shí)時播放,,簡單一點(diǎn)講就是我用網(wǎng)頁在那一邊講話,一個大喇叭就能實(shí)時把我的話播出去,,這樣是不是通俗易懂呀,,而且呢公司要求用mp3格式。當(dāng)然啦,!為了知道自己在講話需要一個波形圖,,這里主要實(shí)現(xiàn)前半部分功能,后半部分臣妾也做不到呀,!后半部分的vlc播放呢如果大家想知道,,可以留言,屆時可以給大家指條明路
前端實(shí)現(xiàn):
引入:<script type="text/javascript" src="/js/recorder/recordmp3.js"></script>
這個跟大佬的js有點(diǎn)不一樣,,我在里面加了一點(diǎn)東西,,而且在這個js里面引入了兩個另外的js,lame.min.js和worker-realtime.js,,這倆在大佬的代碼里有
2020-08-12補(bǔ)充:由于大佬們的博客不見了,,現(xiàn)在補(bǔ)充上兩個js文件worker-realtime.js,lame.min.js
2020-12-25補(bǔ)充:啊啊??!找到了大佬的項(xiàng)目關(guān)于實(shí)時推流啥的,感覺就是啥都行啊,,在此分享https://github.com/xiangyuecn/Recorder,,超級好用,值得學(xué)習(xí)
頁面:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www./1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"/> <title>測試</title> </head> <body> <button id="intercomBegin">開始對講</button> <button id="intercomEnd">關(guān)閉對講</button> <canvas id="casvased" style="width: 400px;height: 100px"></canvas> </body> <script type="text/javascript" src="/js/jquery-3.3.1.js"></script> <script type="text/javascript" src="/js/recorder/recordmp3.js"></script> <script type="text/javascript"> var begin = document.getElementById('intercomBegin'); var end = document.getElementById('intercomEnd'); var canvas = document.getElementById("casvased"); var canvasCtx = canvas.getContext("2d"); var ws = null; //實(shí)現(xiàn)WebSocket var recorder; /* * WebSocket */ function useWebSocket() { ws = new WebSocket("ws://127.0.0.1:8089/send/voice"); ws.binaryType = 'arraybuffer'; //傳輸?shù)氖?ArrayBuffer 類型的數(shù)據(jù) ws.onopen = function () { console.log('握手成功'); if (ws.readyState == 1) { //ws進(jìn)入連接狀態(tài),,則每隔500毫秒發(fā)送一包數(shù)據(jù) recorder.start(); } }; ws.onmessage = function (msg) { console.info(msg) } ws.onerror = function (err) { console.info(err) } } /* * 開始對講 */ begin.onclick = function () { recorder = new MP3Recorder({ debug: true, funOk: function () { console.log('點(diǎn)擊錄制,,開始錄音! '); }, funCancel: function (msg) { console.log(msg); recorder = null; } }); } /* * 關(guān)閉對講 */ end.onclick = function () { if (ws) { ws.close(); recorder.stop(); console.log('關(guān)閉對講以及WebSocket'); } } var sendData = function() { //對以獲取的數(shù)據(jù)進(jìn)行處理(分包) 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); } } } }; recorder.getMp3Blob(function (blob) { reader.readAsArrayBuffer(blob);//這里拿到mp3格式的音頻流寫入到reader中 }; </script> </html> recordmp3.js (function (exports) { var MP3Recorder = function (config) { var recorder = this; config = config || {}; config.sampleRate = config.sampleRate || 44100; config.bitRate = config.bitRate || 128; navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; if (navigator.getUserMedia) { navigator.getUserMedia({ audio: true }, function (stream) { var context = new AudioContext(), microphone = context.createMediaStreamSource(stream), processor = context.createScriptProcessor(16384, 1, 1),//bufferSize大小,,輸入channel數(shù),,輸出channel數(shù) mp3ReceiveSuccess, currentErrorCallback; var height = 100; var width = 400; const analyser = context.createAnalyser() analyser.fftSize = 1024 //連接到音頻源 microphone.connect(analyser); analyser.connect(context.destination); const bufferLength = analyser.frequencyBinCount // 返回的是 analyser的fftsize的一半 const dataArray = new Uint8Array(bufferLength); function draw() { canvasCtx.clearRect(0, 0, width, height); //清除畫布 analyser.getByteFrequencyData(dataArray); // 將當(dāng)前頻率數(shù)據(jù)復(fù)制到傳入其中的Uint8Array const requestAnimFrame = window.requestAnimationFrame(draw) || window.webkitRequestAnimationFrame(draw); canvasCtx.fillStyle = '#000130'; canvasCtx.fillRect(0, 0, width, height); let barWidth = (width / bufferLength) * 2; let barHeight; let x = 0; let c = 2 for (let i = 0; i < bufferLength; i++) { barHeight = c+(dataArray[i]/400)*height; canvasCtx.fillStyle = 'rgb(0, 255, 30)'; canvasCtx.fillRect(x, height / 2 - barHeight / 2, barWidth, barHeight); x += barWidth + 1; } } draw(); useWebSocket(); config.sampleRate = context.sampleRate; processor.onaudioprocess = function (event) { //邊錄音邊轉(zhuǎn)換 var array = event.inputBuffer.getChannelData(0); realTimeWorker.postMessage({cmd: 'encode', buf: array}); sendData(); }; var realTimeWorker = new Worker('/js/recorder/worker-realtime.js'); realTimeWorker.onmessage = function (e) { switch (e.data.cmd) { case 'init': log('初始化成功'); if (config.funOk) { config.funOk(); } break; case 'end': log('MP3大?。?, e.data.buf.length); if (mp3ReceiveSuccess) { mp3ReceiveSuccess(new Blob(e.data.buf, {type: 'audio/mp3'})); } break; case 'error': log('錯誤信息:' + e.data.error); if (currentErrorCallback) { currentErrorCallback(e.data.error); } break; default: log('未知信息:', e.data); } }; recorder.getMp3Blob = function (onSuccess, onError) { currentErrorCallback = onError; mp3ReceiveSuccess = onSuccess; realTimeWorker.postMessage({cmd: 'finish'}); }; recorder.start = function () { if (processor && microphone) { microphone.connect(processor); processor.connect(context.destination); log('開始錄音'); } } recorder.stop = function () { if (processor && microphone) { microphone.disconnect(); processor.disconnect(); log('錄音結(jié)束'); } } realTimeWorker.postMessage({ cmd: 'init', config: { sampleRate: config.sampleRate, bitRate: config.bitRate } }); }, function (error) { var msg; switch (error.code || error.name) { case 'PERMISSION_DENIED': case 'PermissionDeniedError': msg = '用戶拒絕訪問麥客風(fēng)'; break; case 'NOT_SUPPORTED_ERROR': case 'NotSupportedError': msg = '瀏覽器不支持麥客風(fēng)'; break; case 'MANDATORY_UNSATISFIED_ERROR': case 'MandatoryUnsatisfiedError': msg = '找不到麥客風(fēng)設(shè)備'; break; default: msg = '無法打開麥克風(fēng),異常信息:' + (error.code || error.name); break; } if (config.funCancel) { config.funCancel(msg); } }); } else { if (config.funCancel) { config.funCancel('當(dāng)前瀏覽器不支持錄音功能'); } } function log(str) { if (config.debug) { console.log(str); } } } exports.MP3Recorder = MP3Recorder; })(window); 后端websocket: 這里實(shí)現(xiàn)的是保存為mp3文件 package com.jetosend.common.socket; import com.jetosend.common.utils.Utils; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.*; import java.nio.ByteBuffer; import java.util.Hashtable; import java.util.Map; @ServerEndpoint("/send/{key}") @Component public class ServerSocket { private static final Map<String, Session> connections = new Hashtable<>(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); /*** * @Description:打開連接 * @Param: [id, 保存對方平臺的資源編碼 * session] * @Return: void * @Author: Liting * @Date: 2019-10-10 09:22 */ @OnOpen public void onOpen(@PathParam("key") String id, Session session) { System.out.println(id + "連上了"); connections.put(id, session); } /** * 接收消息 */ @OnMessage public void onMessage(@PathParam("key") String id, InputStream inputStream) { System.out.println("來自" + id); try { int rc = 0; byte[] buff = new byte[100]; while ((rc = inputStream.read(buff, 0, 100)) > 0) { byteArrayOutputStream.write(buff, 0, rc); } } catch (Exception e) { e.printStackTrace(); } } /** * 異常處理 * * @param throwable */ @OnError public void onError(Throwable throwable) { throwable.printStackTrace(); //TODO 日志打印異常 } /** * 關(guān)閉連接 */ @OnClose public void onClose(@PathParam("key") String id) { System.out.println(id + "斷開"); BufferedOutputStream bos = null; FileOutputStream fos = null; File file = null; try { file = new File("D:\\testtest.mp3"); //輸出流 fos = new FileOutputStream(file); //緩沖流 bos = new BufferedOutputStream(fos); //將字節(jié)數(shù)組寫出 bos.write(byteArrayOutputStream.toByteArray()); } catch (Exception e) { e.printStackTrace(); } finally { if (bos != null) { try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } connections.remove(id); } 實(shí)現(xiàn)效果:
好了,,到這里就是全部了,,如有疑問可以留言
|
|