如何在javascript中实现抖动缓冲区以进行实时音频处理

How can I implement a jitter buffer in javascript for realtime audio processing

提问人:JSmith 提问时间:4/28/2023 最后编辑:JSmith 更新时间:5/5/2023 访问量:272

问:

我有一个发送器和一个在本地主机上运行的接收者

发射和接收的平均间隔分别为 2.89 和 2.92 毫秒。

正确的常数区间应为 2.90;

因此,我尝试实现某种环形缓冲区,其中缓冲区大小为 128,是数据块的大小和内存中保存的数据块的数量(延迟)。128 * NN

我还没有评估接收和处理之间的时间间隔,但根据我设法计算的数据,大小的缓冲区应该足够了。N = 2

此外,我的缓冲区非常简单,它是一个 FIFO,如果在缓冲区已满时接收到值,它会替换前一个块。

为了获得正确的声音,我需要使缓冲区大小为 所以我的问题是如何实现一个可能具有自适应性的低延迟抖动缓冲区N = 16

我想目前缓冲区增加了额外的内存来管理平均变化,但我需要的是一种“就地”纠正变化的技术。

我目前的实现

_buffer = [];

BUFFER_SIZE = 8;

_isStarted = false;

readIndex = -1

//this callback is triggered any time a packet is received.

_onReceivePacket = ( event ) => {

    let chunks = [];

    //chunk length = 128

    for ( let chunk of event.data ) {

      chunks.push( new Float32Array( chunk ) );

    }

    if ( this._buffer.length < this.BUFFER_SIZE ) {

      this._buffer.unshift( chunks );

      this.readIndex++;

    } else {

      this._buffer.splice( 0, 1 );

      this._buffer.unshift( chunks );

      this._isStarted = true;

    }

}
  //this function copies the buffer into the playout output stream

  _pullOut ( output ) {

    try {

      for ( let i = 0; i < output.length; i++ ) {

        const channel = output[ i ];

        for ( let j = 0; j < channel.length; j++ ) {

          channel[ j ] = this._buffer[ this.readIndex ][ i ][ j ];

        }

      }

      if ( this.readIndex - 1 !== -1 ) {

        this._buffer.splice( this.readIndex, 1 );

        this.readIndex--;

      }

    } catch ( e ) {

      console.log( e, this._buffer, this.readIndex );

    }

  }
JavaScript 音频 VoIP 抖动

评论

0赞 Kyle 5/1/2023
你有VOIP作为你的标签之一,你在做VOIP吗?如果是这样,您不使用 WebRTC 有什么原因吗?
0赞 JSmith 5/1/2023
@Kyle这不是问题。
0赞 Kyle 5/1/2023
你在什么环境中运行它?它是 Web 浏览器、NodeJS、电子吗?
0赞 JSmith 5/1/2023
Web 浏览器和 AudioWorklet 处理器。提前致谢
1赞 JSmith 5/6/2023
@Kylethank你的时间,好吧,如果你能重写它,我总是很感兴趣。在我这边,我删除了代码来测试抖动缓冲区,因为我在旧的 mackbook 笔记本电脑上的所有尝试都失败了(我做了比帖子上更复杂的缓冲区)。但我很乐意测试你的。再次感谢。

答:

0赞 Pradnya Kulkarni 5/5/2023 #1

你可以用 JavaScript 编写一些类,如下所示

class JitterBuffer {
  constructor(bufferSize, latency) {
    this.bufferSize = bufferSize; // Size of each data chunk
    this.latency = latency; // Number of data chunks kept in memory
    this.buffer = new Array(latency);
    this.head = 0; // Index of the next available slot in the buffer
    this.tail = 0; // Index of the next data chunk to be processed
    this.fillCount = 0; // Number of data chunks currently in the buffer
    this.interval = 2.90; // Target time interval between data chunks
    this.timer = null; // Reference to the setInterval timer
  }

  start() {
    // Start the timer to check for buffer underflow and adjust the fillCount
    this.timer = setInterval(() => {
      if (this.fillCount === 0) {
        return;
      }

      const now = Date.now();
      const expectedTime = this.tail * this.interval;
      const actualTime = now - this.buffer[this.tail].timestamp;

      if (actualTime >= expectedTime) {
        // Remove the oldest data chunk and update the tail index
        this.tail = (this.tail + 1) % this.latency;
        this.fillCount--;
      }
    }, Math.floor(this.interval / 2));
  }

  stop() {
    // Stop the timer
    clearInterval(this.timer);
    this.timer = null;
  }

  push(data) {
    // Add the data chunk to the buffer and update the head index
    this.buffer[this.head] = {
      data,
      timestamp: Date.now(),
    };
    this.head = (this.head + 1) % this.latency;

    if (this.fillCount < this.latency) {
      // Increment the fillCount if the buffer is not full yet
      this.fillCount++;
    } else {
      // Replace the oldest data chunk and update the tail index
      this.tail = (this.tail + 1) % this.latency;
    }
  }

  shift() {
    if (this.fillCount === 0) {
      return null;
    }

    // Get the next data chunk to be processed and update the tail index
    const dataChunk = this.buffer[this.tail].data;
    this.tail = (this.tail + 1) % this.latency;
    this.fillCount--;

    return dataChunk;
  }

  adjustInterval(actualInterval) {
    // Adjust the target time interval based on the actual interval
    const delta = actualInterval - this.interval;
    this.interval += delta / 8; // Adapt to 1/8th of the difference
  }
}

它可以像下面这样使用:

// Create a jitter buffer with a buffer size of 128 bytes per chunk and a latency of 16 chunks
const jitterBuffer = new JitterBuffer(128, 16);
// Start the jitter buffer timer
jitterBuffer.start();

// Add some data to the jitter buffer
const data = new Uint8Array(128);
jitterBuffer.push(data);

// Retrieve the next data chunk from the jitter buffer
const nextChunk = jitterBuffer.shift();

// Stop the jitter buffer timer
jitterBuffer.stop();

评论

0赞 JSmith 5/6/2023
感谢您抽出宝贵时间接受采访。我有时间的时候会测试一下。