Javascript AudioWorklet-将输出设置为Float32Array以流式传输实时音频?

Javascript AudioWorklet-将输出设置为Float32Array以流式传输实时音频?,javascript,node.js,audio-streaming,web-audio-api,audio-processing,Javascript,Node.js,Audio Streaming,Web Audio Api,Audio Processing,我有从服务器到客户端的音频数据流。它以Node.js缓冲区(Uint8Array)开始,然后通过port.postMessage()发送到AudiWorkletProcessor,在那里它被转换为Float32Array并存储在this.data中。我花了数小时试图将输出设置为Float32Array中包含的音频数据。记录Float32Array预处理可以显示准确的数据,但在处理过程中记录数据表明,在发布新消息时,数据不会改变。这可能是我低级音频编程知识的一个缺口 当数据到达客户端时,调用以下函

我有从服务器到客户端的音频数据流。它以Node.js缓冲区(Uint8Array)开始,然后通过port.postMessage()发送到AudiWorkletProcessor,在那里它被转换为Float32Array并存储在this.data中。我花了数小时试图将输出设置为Float32Array中包含的音频数据。记录Float32Array预处理可以显示准确的数据,但在处理过程中记录数据表明,在发布新消息时,数据不会改变。这可能是我低级音频编程知识的一个缺口

当数据到达客户端时,调用以下函数:

  process = (data) => {
        this.node.port.postMessage(data)
  }
另外,(你可以让我知道)也许我应该使用参数描述符而不是postMessage?无论如何,这是我的AudioWorkletProcessor:

class BypassProcessor extends AudioWorkletProcessor {

  constructor() {
    super();
    this.isPlaying = true;
    this.port.onmessage = this.onmessage.bind(this)
  }

  static get parameterDescriptors() {
    return [{ // Maybe we should use parameters. This is not utilized at present.
      name: 'stream',
      defaultValue: 0.707
    }];
  }

  convertBlock = (incomingData) => { // incoming data is a UInt8Array
    let i, l = incomingData.length;
    let outputData = new Float32Array(incomingData.length);
    for (i = 0; i < l; i++) {
      outputData[i] = (incomingData[i] - 128) / 128.0;
    }
    return outputData;
  }

  onmessage(event) {
    const { data } = event;
    let ui8 = new Uint8Array(data);
    this.data = this.convertBlock(ui8)
  }

  process(inputs, outputs) {
    const input = inputs[0];
    const output = outputs[0];
    if (this.data) {
      for (let channel = 0; channel < output.length; ++channel) {
        const inputChannel = input[channel]
        const outputChannel = output[channel]
        for (let i = 0; i < inputChannel.length; ++i) {
          outputChannel[i] = this.data[i]
        }
      }
    }

    return true;

  }
}

registerProcessor('bypass-processor', BypassProcessor);
类处理器扩展了AudioWorkletProcessor{ 构造函数(){ 超级(); this.isplay=true; this.port.onmessage=this.onmessage.bind(this) } 静态获取参数描述符(){ return[{//也许我们应该使用参数。目前还没有使用。 名称:'流', 默认值:0.707 }]; } convertBlock=(incomingData)=>{//传入数据是UINT8数组 设i,l=incomingData.length; 让outputData=newfloat32array(incomingData.length); 对于(i=0;i
如何简单地将AudioWorkletProcessor的输出设置为通过的数据?

AudioWorkletProcessor仅处理每128个字节,因此您需要管理自己的缓冲区,以确保这是适用于
AudioWorklet
的情况,可能需要添加FIFO。 我使用WebAssembly中实现的RingBuffer(FIFO)解决了类似的问题,在我的例子中,我接收到一个160字节的缓冲区

查看我的AudioWorkletProcessor实现

import Module from './buffer-kernel.wasmodule.js';
import { HeapAudioBuffer, RingBuffer, ALAW_TO_LINEAR } from './audio-helper.js';

class SpeakerWorkletProcessor extends AudioWorkletProcessor {
  constructor(options) {
    super();
    this.payload = null;
    this.bufferSize = options.processorOptions.bufferSize; // Getting buffer size from options
    this.channelCount = options.processorOptions.channelCount;
    this.inputRingBuffer = new RingBuffer(this.bufferSize, this.channelCount);
    this.outputRingBuffer = new RingBuffer(this.bufferSize, this.channelCount);
    this.heapInputBuffer = new HeapAudioBuffer(Module, this.bufferSize, this.channelCount);
    this.heapOutputBuffer = new HeapAudioBuffer(Module, this.bufferSize, this.channelCount);
    this.kernel = new Module.VariableBufferKernel(this.bufferSize);
    this.port.onmessage = this.onmessage.bind(this);
  }

  alawToLinear(incomingData) {
    const outputData = new Float32Array(incomingData.length);
    for (let i = 0; i < incomingData.length; i++) {
      outputData[i] = (ALAW_TO_LINEAR[incomingData[i]] * 1.0) / 32768;
    }
    return outputData;
  }

  onmessage(event) {
    const { data } = event;
    if (data) {
      this.payload = this.alawToLinear(new Uint8Array(data)); //Receiving data from my Socket listener and in my case converting PCM alaw to linear
    } else {
      this.payload = null;
    }
  }

  process(inputs, outputs) {
    const output = outputs[0];
    if (this.payload) {
      this.inputRingBuffer.push([this.payload]); // Pushing data from my Socket

      if (this.inputRingBuffer.framesAvailable >= this.bufferSize) { // if the input data size hits the buffer size, so I can "outputted"  
        this.inputRingBuffer.pull(this.heapInputBuffer.getChannelData());
        this.kernel.process(
          this.heapInputBuffer.getHeapAddress(),
          this.heapOutputBuffer.getHeapAddress(),
          this.channelCount,
        );
        this.outputRingBuffer.push(this.heapOutputBuffer.getChannelData());
      }
      this.outputRingBuffer.pull(output); // Retriving data from FIFO and putting our output
    }
    return true;
  }
}

registerProcessor(`speaker-worklet-processor`, SpeakerWorkletProcessor);

看看我是如何将数据从套接字接收和发送到audioWorklet的

  protected onMessage(e: any): void { //My Socket message listener
    const { data:serverData } = e;
    const socketId = e.socketId;
    if (this.audioWalking && this.ws && !this.ws.isPaused() && this.ws.info.socketId === socketId) {
      const buffer = arrayBufferToBuffer(serverData);
      const rtp = RTPParser.parseRtpPacket(buffer);
      const sharedPayload = new Uint8Array(new SharedArrayBuffer(rtp.payload.length)); //sharing javascript buffer memory between main thread and worklet thread
      sharedPayload.set(rtp.payload, 0);
      this.speakerWorklet.port.postMessage(sharedPayload); //Sending data to worklet
    }
  } 
为了帮助人们,我将此解决方案的重要部分放在Github上

我遵循了这个示例,它对RingBuffer的工作原理进行了全面的解释


嗨,我也有同样的问题,你明白了吗?@vmontanheiro不,很遗憾。我可以在没有AudioWorklet的情况下完成它,不管我是如何得到它的!您是否从服务器提交的RTP数据包中获取数据??您是否使用SharedArrayBuffer将消息发布到您的流程中?不要失去希望。哈哈!如果有什么方法可以让我根据SharedArrayBuffer模式选择你的大脑,一定要联系!目前,从serverSharedArrayBuffer发送数据包是一种非常普通的模式,因为它不会产生额外的垃圾,但我认为您的问题与我的相同。我的数据长度是160字节,但AudioWorkletProcessor只处理每个128字节,因此我尝试使用缓冲环(FIFO)来管理它,以返回具有正确大小的缓冲区。当我完成后,我会和你们分享。做得很好,谢谢你们给出这个答案!我将对此进行研究,看看是否可以应用到我的用例中。虽然没有使用RTP(webRTC),但我认为它仍然适用。不客气!!你会发抖,音频输出绝对实时,没有任何延迟。我没有使用webRTC,我使用的是UDP套接字并处理自己的RTP头。祝你好运:DHey,你知道我如何将AudioWorklet与GainNode一起使用吗?我现在需要控制音量。我认为谷歌关于引入AudioWorklet的文章有很好的例子,可以使用参数描述符来完成类似的事情。我在打电话,所以有机会的时候我会尝试发布链接
  protected onMessage(e: any): void { //My Socket message listener
    const { data:serverData } = e;
    const socketId = e.socketId;
    if (this.audioWalking && this.ws && !this.ws.isPaused() && this.ws.info.socketId === socketId) {
      const buffer = arrayBufferToBuffer(serverData);
      const rtp = RTPParser.parseRtpPacket(buffer);
      const sharedPayload = new Uint8Array(new SharedArrayBuffer(rtp.payload.length)); //sharing javascript buffer memory between main thread and worklet thread
      sharedPayload.set(rtp.payload, 0);
      this.speakerWorklet.port.postMessage(sharedPayload); //Sending data to worklet
    }
  }