Я делал голосовую беседу между двумя серверами node.js раньше (см.: tvoip), которая работает довольно хорошо, но теперь я хотел бы сделать это между сервером node.js и браузером. Как это можно сделать?
От node.js до node.js я просто использовал raw PCM-потоки по TCP-соединению.
Для браузера это, вероятно, будет не так просто, не так ли? Я имею в виду, что браузер не предлагает API TCP. Он предлагает API WebSocket, но обрабатывает ли он потоки? Должен ли я преобразовать потоки, и если да, то в какой формат и как? Какой протокол я должен использовать? Есть ли полезные библиотеки для этого? Является ли socket.io-stream жизнеспособной библиотекой для отправки таких потоков?
Насколько я понимаю, аудиопотоки находятся в формате PCM в браузере. Поэтому он должен быть совместим с потоками, которые я получил в Node.js. Правильно ли это предположение?
Мне удалось подключить входной микрофон браузера к выходу динамика браузера следующим образом:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<!-- alternative method that also works
<audio></audio>
<script>
navigator.mediaDevices.getUserMedia({ audio: true }).then(function(stream) {
const audio = document.querySelector('audio')
audio.srcObject = stream
audio.onloadedmetadata = function(e) {
audio.play()
}
}).catch(console.error)
</script>
-->
<script>
navigator.mediaDevices.getUserMedia({audio: true}).then(stream => {
const aCtx = new AudioContext()
const analyser = aCtx.createAnalyser()
const microphone = aCtx.createMediaStreamSource(stream)
microphone.connect(analyser)
analyser.connect(aCtx.destination)
}).catch(err => {
console.error("Error getting audio stream from getUserMedia")
})
</script>
</body>
</html>
Как вы можете видеть, я нашел два решения. Я постараюсь основать голосовой чат браузера узла <-> на втором.
Для Node.js я придумал этот код, чтобы подключить входной сигнал node.js mic к выходу динамика node.js:
const mic = require('mic')
const Speaker = require('speaker')
const micInstance = mic({ // arecord -D hw:0,0 -f S16_LE -r 44100 -c 2
device: 'hw:2,0', // -D hw:0,0
encoding: 'signed-integer', // -f S
bitwidth: '16', // 16
endian: 'little', // _LE
rate: '44100', // -r 44100
channels: '1', // -c 2
debug: true
})
const micInputStream = micInstance.getAudioStream()
const speakerInstance = new Speaker({ // | aplay -D plughw:CARD=0,DEV=0
channels: 1,
bitDepth: 16,
sampleRate: 44100,
signed: true,
device: 'plughw:2,0' //'plughw:NVidia,7'
})
speakerInstance.on('open', ()=>{
console.log("Speaker received stuff")
})
// Pipe the readable microphone stream to the writable speaker stream:
micInputStream.pipe(speakerInstance)
micInputStream.on('data', data => {
//console.log("Recieved Input Stream: " + data.length)
})
micInputStream.on('error', err => {
cosole.log("Error in Input Stream: " + err)
})
micInstance.start()
console.log('Started')
Поиск подходящего device
для микрофона и динамика может быть немного сложным, если вы не знакомы с ALSA под Linux. Это объясняется здесь, если вы не уверены. Я не уверен, как это работает на Windows и Mac OS с помощью SoX.
Затем я придумал небольшое тестовое приложение для подключения двух идей, используя socket.io-stream (библиотека socket.io, которая позволяет отправлять потоки через сокет). И, очевидно, здесь я застрял.
В принципе, я пробую это на стороне node.js:
const mic = require('mic')
const Speaker = require('speaker')
const SocketIO = require('socket.io')
const ss = require('socket.io-stream')
...
io.on('connection', socket => {
let micInstance = mic(micConfig)
let micInputStream = micInstance.getAudioStream()
let speakerInstance = new Speaker(speakerConfig)
...
ss(socket).on('client-connect', (stream, data) => { // stream: duplex stream
stream.pipe(speakerInstance) //speakerInstance: writable stream
micInputStream.pipe(stream) //micInputStream: readable stream
micInstance.start()
})
})
и это на стороне браузера:
const socket = io()
navigator.mediaDevices.getUserMedia({audio:true}).then(clientMicStream => { // Get microphone input
// Create a duplex stream using the socket.io-stream library ss.createStream() method and emit it it to the server
const stream = ss.createStream() //stream: duplex stream
ss(socket).emit('client-connect', stream)
// Send microphone input to the server by piping it into the stream
clientMicStream.pipe(stream) //clientMicStream: readable stream
// Play audio received from the server through the stream
const aCtx = new AudioContext()
const analyser = aCtx.createAnalyser()
const microphone = aCtx.createMediaStreamSource(stream)
microphone.connect(analyser)
analyser.connect(aCtx.destination)
}).catch(e => {
console.error('Error capturing audio.')
alert('Error capturing audio.')
})
Весь код можно просмотреть по адресу: https://github.com/T-vK/node-browser-audio-stream-test
(README.md содержит инструкции по настройке, если вы хотите проверить его.) Соответствующий код находится в server.js (функция setupStream() содержит интересный код.) И client.html.
Как вы можете видеть, я пытаюсь отправить дуплексный поток по соединению и подключать микрофонные входы к дуплексному потоку и направлять дуплексный поток на динамик на каждом конце (например, я сделал это в tvoip). Однако это не работает.
Редактировать:
Я не уверен, что я прав, но "поток", который я получаю от getUserMedia(), - это MediaStream, и этот медиапоток может иметь MediaStreamTrack (аудио, видео или и то, и другое). Я в своем случае, это, очевидно, просто один трек (аудио). Но MediaStreamTrack
, похоже, не является потоком, так как я знаю его из Node.js, а это значит, что его нельзя просто пропустить. Поэтому, возможно, его нужно было бы преобразовать в одно. Я нашел эту интересную библиотеку под названием microphone-stream, которая утверждает, что сможет это сделать. Но, похоже, он не доступен как простая библиотека браузера. Кажется, что нужно обойти весь проект с помощью браузера. Который кажется очень overkill. Я бы хотел, чтобы это было просто.