1

I have a sender an a receiver running on localhost

both emit and receive on an average interval of 2.89 and 2.92 milliseconds.

the correct constant interval should be 2.90;

So I tried implementing some sort of ringbuffer where buffer size is 128 * N where 128 is the size of the data chunk and N the number of data chunks kept in memory (latency).

I haven't evaluated the time interval between the receival and the processing but according to the data I've managed to calculate a buffer of size N = 2 should be sufficient.

Also my buffer is quite simple it's a FIFO and if a value is received while the buffer is full it replaces the previous chunk.

To get a correct sound I need to make a buffer size of N = 16 so my question is how can I implement a low latency jitter buffer that may be adaptative

I guess currently the buffer adds extra memory for managing an average variation but what I would need is a technique to correct the variation "in place".

my current implementation

_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 );

    }

  }
JSmith
  • 4,519
  • 4
  • 29
  • 45
  • You have VOIP as one of your tags, are you doing VOIP? If so, is there a reason you aren't using WebRTC? – Kyle May 01 '23 at 13:31
  • @Kyle this is not the question. – JSmith May 01 '23 at 13:59
  • What environment are you running this in? Is it a web browser, NodeJS, electron? – Kyle May 01 '23 at 14:03
  • web browser and audioworklet processor.Thanks in advance – JSmith May 01 '23 at 14:13
  • I'll see if I can get to this in the next couple of days. Didn't want to leave you hanging if you thought I'd answer today. – Kyle May 01 '23 at 14:40
  • @kyle no pressure let me show you [this document](https://patents.google.com/patent/US9660887B1/en?q=(adaptative+audio+stream)&oq=adaptative+audio+stream+with). Unfortunately I wasn't able to reproduce it.Thanks again – JSmith May 01 '23 at 14:46
  • I haven't really had time to look at this, and truth be told, I missed the part where it said that the buffer can't expand. One thought I had was to either re-write speexdsp's jitterbuffer, or compile it to JS using emscripten. https://gitlab.xiph.org/xiph/speexdsp/-/blob/master/libspeexdsp/jitter.c – Kyle May 05 '23 at 12:16
  • 1
    @Kylethank you for your time well if you can rewrite it I'm always interested. On my side I removed the code to test the jitter buffer as all my tries failed on my old mackbook laptop (I've done more elaborated buffers than the one on the post).But I'll be glad to test yours.Thanks again. – JSmith May 06 '23 at 04:34

1 Answers1

0

you can write some class in javascript like below

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
  }
}

It can be used like below:

// 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();