实现一个简单的实时对讲功能,将一台电脑的语音实时传输到另一台电脑并播放。
Socket转发
websocket可以直接转发音频流,无需做更多处理
let WebSocketServer = require('ws').Server
let WebSocket = require('ws')
const wss = new WebSocketServer({ port: 1041 });//服务端口8181
wss.on('connection', function (ws) {
console.log('客户端已连接');
ws.on('message', (data, isBinary) => {
// 收到消息以后,转发给所有连接的客户端
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data, { binary: isBinary });
}
});
});
});
通过麦克风获取声音并传输
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="start">start</button>
<button id="stop">startstop</button>
<script>
// 连接 websocket
const ws = new WebSocket('ws://192.168.220.223:1041')
ws.onopen = () => {
console.log('socket 已连接')
}
ws.onerror = (e) => {
console.log('error', e);
}
ws.onclose = () => {
console.log('socket closed')
}
document.getElementById('start').onclick = function () {
// 该变量存储当前MediaStreamAudioSourceNode的引用
// 可以通过它关闭麦克风停止音频传输
let mediaStack
let audioCtx = new AudioContext();
// 创建一个ScriptProcessorNode 用于接收当前麦克风的音频
let scriptNode = audioCtx.createScriptProcessor(4096, 1, 1);
navigator.mediaDevices.getUserMedia({ audio: true, video: false })
.then(function (stream) {
mediaStack = stream
var source = audioCtx.createMediaStreamSource(stream)
source.connect(scriptNode);
scriptNode.connect(audioCtx.destination);
})
.catch(function (err) {
/* 处理error */
console.log('err', err)
});
// 当麦克风有声音输入时,会调用此事件
// 实际上麦克风始终处于打开状态时,即使不说话,此事件也在一直调用
scriptNode.onaudioprocess = function (audioProcessingEvent) {
let inputBuffer = audioProcessingEvent.inputBuffer;
// 由于只创建了一个音轨,这里只取第一个频道的数据
let inputData = inputBuffer.getChannelData(0);
console.log(inputData);
// 通过socket传输数据,实际上传输的是Float32Array
ws.send(inputData)
}
// 关闭麦克风
document.getElementById('startstop').onclick = function () {
mediaStack.getTracks()[0].stop()
scriptNode.disconnect()
};
}
</script>
</body>
</html>
获取socket传输过来的音频流并播放
处理方式一
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button onclick="play()">play</button>
<script>
function play() {
const audioCtx = new AudioContext();
// 连接socket
const ws = new WebSocket('ws://127.0.0.1:1041')
ws.onopen = () => {
console.log('socket opened')
}
// 接收的数据类型是arraybuffer
ws.binaryType = 'arraybuffer'
ws.onmessage = ({data}) => {
// 将接收的数据转换成与传输过来的数据相同的Float32Array
const buffer = new Float32Array(data)
// 创建一个空白的AudioBuffer对象,这里的4096跟发送方保持一致,48000是采样率
const myArrayBuffer = audioCtx.createBuffer(1, 4096, 48000);
// 也是由于只创建了一个音轨,可以直接取到0
const nowBuffering = myArrayBuffer.getChannelData(0);
// 通过循环,将接收过来的数据赋值给简单音频对象
for (let i = 0; i < 4096; i++) {
nowBuffering[i] = buffer[i];
}
// 使用AudioBufferSourceNode播放音频
const source = audioCtx.createBufferSource();
source.buffer = myArrayBuffer
source.connect(audioCtx.destination);
source.start();
}
ws.onerror = (e) => {
console.log('error', e);
}
ws.onclose = () => {
console.log('socket closed');
}
}
</script>
</body>
</html>
处理方式二
<html>
<head>
</head>
<body>
<script>
var audio_context, gain_node, source_node;
var chosen_audio_file_url = "output_2.wav";
// log if an error occurs
function on_error(e) {
console.log("ERROR - " + e);
}
try {
window.AudioContext = window.AudioContext ||
window.webkitAudioContext ||
window.mozAudioContext ||
window.oAudioContext ||
window.msAudioContext;
audio_context = new AudioContext();
console.log("cool audio context established");
} catch (e) {
alert("Web Audio API is not supported by this browser");
}
gain_node = audio_context.createGain(); // Declare gain node
gain_node.connect(audio_context.destination); // Connect gain node to speakers
source_node = audio_context.createBufferSource();
source_node.connect(gain_node);
var request = new XMLHttpRequest();
request.open('GET', chosen_audio_file_url, true);
request.responseType = 'arraybuffer';
// When loaded decode the data
request.onload = function() {
// decode the data
audio_context.decodeAudioData(request.response, function(buffer) {
console.log(chosen_audio_file_url, ' ... buffer.length ', buffer.length);
source_node.buffer = buffer;
source_node.start(0);
// ---
}, on_error);
}
function run() {
request.send();
}
</script>
<a href="javascript:run()">run</a>
</body>
</html>
Web Audio之getChannelData
// 两个通道,也就是立体声
let channels = 2;
// 总共2s,乘以sampleRate采样率就是总的PCM数据长度
let frameCount = audioCtx.sampleRate * 2.0;
// 创建一个buffer,三个参数分别是通道数,存贮在缓冲区中的PCM数据的长度,采样率
let myArrayBuffer = audioCtx.createBuffer(channels, frameCount, audioCtx.sampleRate);
button.onclick = function() {
// 对每一个频道都填充白噪音
for (var channel = 0; channel < channels; channel++) {
// 当前的频道,总共填充两个频道
var nowBuffering = myArrayBuffer.getChannelData(channel);
for (let i = 0; i < frameCount; i++) {
// 对当前频道填充-1到1的随机数
nowBuffering[i] = Math.random() * 2 - 1;
}
}
// 创建一个AudioBuffer的容器,也就是AudioBufferSourceNode
let source = audioCtx.createBufferSource();
source.buffer = myArrayBuffer;
// 链接到总输出
source.connect(audioCtx.destination);
// 播放
source.start();
}