2016-11-03 5 views
0

Я использую сокеты Unix для потоковой передачи звука с моего микрофона (через приложение Electron NodeJS) в программу python, прослушивающую сокет и отправляющую аудио в PyAudio для воспроизведения.Формат и отправка бесшовного звука от JS к локальному PyAudio

Electron приложение getUserMedia() => WAV форматирования => NodeJS сокет => Unix сокет => Python Оправа => PyAudio

У меня он работает, но есть постоянный щелкающий звук, когда каждый блок начинается или заканчивается. Где я должен начать отладку? Вот код:

NodeJS приложение (отправитель):

var net = require('net'); 
const nodeWav = require("node-wav"); 

var recorder = null; 
var volume = null; 
var audioInput = null; 
var sampleRate = null; 
var audioContext = null; 
var context = null; 
var outputElement = document.getElementById('output'); 
var outputString; 
var bufferSize = 1024; 

var mediaSourceIn; 

// callback for navigator.mediaDevices.getUserMedia() 
function audioReceiver(e) { 
    // creates Socket 
    mediaSourceIn = e; 
    initSocket(); 
} 

var audioSocket; 
function initSocket() { 
    audioSocket = net.connect('/tmp/audio_input', connected) 
    .catch(function(err) { 
    console.log("Could not connect..."); 
    console.log(err); 
    }); 
} 

function connected() { 
    console.log("CONNECTED TO UNIX SOCKET!"); 
    audioSocket = this; 
    createRecordingTask(); 
} 

function createRecordingTask() { 
    // creates the audio context 
    audioContext = window.AudioContext || window.webkitAudioContext; 
    context = new audioContext(); 

    // retrieve the current sample rate to be used for WAV packaging 
    sampleRate = context.sampleRate; 

    // creates a gain node 
    volume = context.createGain(); 

    // creates an audio node from the microphone incoming stream 
    audioInput = context.createMediaStreamSource(mediaSourceIn); 

    // connect the stream to the gain node 
    audioInput.connect(volume); 

    /* From the spec: This value controls how frequently the audioprocess event is 
    dispatched and how many sample-frames need to be processed each call. 
    Lower values for buffer size will result in a lower (better) latency. 
    Higher values will be necessary to avoid audio breakup and glitches */ 
    recorder = context.createScriptProcessor(bufferSize, 2, 2); 

    recorder.onaudioprocess = function(e){ 
     console.log ('recording'); 
     var left = e.inputBuffer.getChannelData (0); 
     var right = e.inputBuffer.getChannelData (1); 
     var bf = createAudioBuffer(
      new Float32Array (left), 
      new Float32Array (right)); 

     upload(bf); 
    } 

    // we connect the recorder 
    volume.connect (recorder); 
    recorder.connect (context.destination); 
} 

function mergeBuffers(channelBuffer){ 
    var result = new Float32Array(bufferSize); 
    result.set(channelBuffer); // make a copy? 
    return result; 
} 

function interleave(leftChannel, rightChannel){ 
    var length = leftChannel.length + rightChannel.length; 
    var result = new Float32Array(length); 

    var inputIndex = 0; 

    for (var index = 0; index < length;){ 
    result[index++] = leftChannel[inputIndex]; 
    result[index++] = rightChannel[inputIndex]; 
    inputIndex++; 
    } 
    return result; 
} 

function writeUTFBytes(view, offset, string){ 
    var lng = string.length; 
    for (var i = 0; i < lng; i++){ 
    view.setUint8(offset + i, string.charCodeAt(i)); 
    } 
} 

function createAudioBuffer(leftchannel, rightchannel) { 

    // we flat the left and right channels down 
    var leftBuffer = mergeBuffers (leftchannel, bufferSize); 
    var rightBuffer = mergeBuffers (rightchannel, bufferSize); 

    // we interleave both channels together 
    var interleaved = interleave (leftBuffer, rightBuffer); 

    // we create our wav file 
    var buffer = new ArrayBuffer(44 + interleaved.length * 2); 
    //var buffer = new ArrayBuffer(interleaved.length * 2); 
    var view = new DataView(buffer); 

    // RIFF chunk descriptor 
    writeUTFBytes(view, 0, 'RIFF'); 
    view.setUint32(4, 44 + interleaved.length * 2, true); 
    writeUTFBytes(view, 8, 'WAVE'); 
    // FMT sub-chunk 
    writeUTFBytes(view, 12, 'fmt '); 
    view.setUint32(16, 16, true); 
    view.setUint16(20, 1, true); 
    // stereo (2 channels) 
    view.setUint16(22, 2, true); 
    view.setUint32(24, sampleRate, true); 
    view.setUint32(28, sampleRate * 4, true); 
    view.setUint16(32, 4, true); 
    view.setUint16(34, 16, true); 
    // data sub-chunk 
    writeUTFBytes(view, 36, 'data'); 
    view.setUint32(40, interleaved.length * 2, true); 
    // write the PCM samples 
    var lng = interleaved.length; 
    //var index = 0; 
    var index = 44; 
    var volume = 0.6; 
    for (var i = 0; i < lng; i++){ 
     view.setInt16(index, interleaved[i] * (0x7FFF * volume), true); 
     index += 2; 
    } 
    // our final binary blob 
    return Buffer.from(view.buffer); 
} 


function upload(thatAudio) { 
    if (audioSocket.writable) { 
    audioSocket.write(thatAudio); 
    } else { 
    console.log("DISCONNECTED!"); 
    } 
} 

Python программа (приемник):

import socket 
import os 
import pyaudio 
from threading import Thread 

sockfile = "/tmp/audio_input" 

FORMAT = pyaudio.paInt16 
CHUNK = 1024 
CHANNELS = 2 
RATE = 44100 
frames = [] 

if os.path.exists(sockfile): 
    os.remove(sockfile) 

print("Opening socket...") 
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 
server.bind(sockfile) 
server.listen(5) 
conn, addr = server.accept() 

print("Creating PyAudio stream...") 
p = pyaudio.PyAudio() 

stream = p.open(format=FORMAT, 
       channels = CHANNELS, 
       rate = RATE, 
       output = True, 
       frames_per_buffer = CHUNK, 
       ) 

print "Listening..." 
singleChunkSizeBytes = 44 + (CHUNK * CHANNELS*2) 
print singleChunkSizeBytes, "bytes at a time" 
while True: 
    soundData = conn.recv(singleChunkSizeBytes) 
    if soundData: 
     stream.write(soundData, CHUNK) 

server.close() 
os.remove(sockfile) 

ответ

1

Сначала вы должны проверить, если stream.write() вызывает опустошение буфера. Это, вероятно, можно сделать с помощью опции exception_on_underflow (см. docs). Если вы хотите не бросать версию функции write(), вы можете попробовать модуль sounddevice (см. Его write() docs).

Если является недоработками, это может означать, что сокет не обеспечивает данные достаточно быстро. В этом случае вам, вероятно, следует выполнить некоторую буферизацию на стороне приемника, например. используя queue.Queue.

Если нет опустошения, ошибка, вероятно, на передающей стороне ...

+0

Исключения не было брошено, когда exception_on_underflow = верно. Таким образом, WAV-форматирование, вероятно, является виновником. Я попытаюсь просмотреть байты заголовков обычных файлов WAV и посмотреть, соответствуют ли они тому, что я отправляю – mmarkman

+1

ОК, теперь я вижу ... Вы получаете кусок, включая заголовок WAV. Вам нужно будет удалить первые 44 байта перед записью в поток PyAudio! Кроме того, я думаю, 'recv()' может возвращать меньше байтов, чем запрошено, вы должны это проверить. – Matthias

+1

И поскольку вы не проверяете заголовок, вероятно, не имеет большого смысла отправлять его в первую очередь. Вы могли бы просто отправить необработанные данные. – Matthias