2
votes

Hi I have an arrays of PCM data (Float32) buffered on client side (all these audio arrays are parts of the same song) So it is sort of a client side buffering of PCM data. After N numbers of arrays are downloaded from the server I start playing these buffers and call the function below to play the sample arrays.

var AudioStart =0;

function playPcmChunk(data){

    var source = audioContext.createBufferSource();

    var audio=new Float32Array(data);

    var audioBuffer = audioContext.createBuffer(1, audio.length , 44100);

    audioBuffer.getChannelData(0).set(audio);

    source.buffer = audioBuffer;

    source.start(AudioStart);

    AudioStart += audioBuffer.duration;
}

Between everysample I get clicks.

Now to make sure I looked at the data on the server side, It plays fine and smooth with Audacity.

Then to debug I literately printed out the values and compared them on the client side as well as on the server side to see if there is a problem with transport and they are the same.

So why am I getting clicking sounds between buffered arrays of samples.

Is my calculation of the exact time not correct. Is this a known web audio api Issue?

I think this is as precise as I can get. Do I need to apply a filter to get rid of the clicks. What would be the name of that filter ? Even if there is I think that would be hack.

Any ideas are welcome

Edit:

This is where I read off the websocket and store 30(this is totally a random number) of these samples in an array. After 30 of them is stored I start looping on each sample and call playPcmChunk and discard rest of the audio coming in.(this is for testing)

each is 44100 32bitFloat, Mono.

wsAudio.onmessage = function (messageEvent) {
        if (messageEvent.data instanceof Blob) {


            var fileReader = new FileReader();
            fileReader.onload = function (e) {
                if (currentCount == -1) {
                    return;
                }
                if (currentCount < 30) {
                    audioChunks[currentCount] = e.target.result;
                    currentCount++;
                } else {

                    for (var j = 0; j < 30; j++) {

                        playPcmChunk(audioChunks[j]);
                    }
                    currentCount = -1;
                }



            };
            fileReader.readAsArrayBuffer(messageEvent.data);



        }
    }
2
Need to see the code that's calling playPcmChunk.Kevin Ennis

2 Answers

2
votes

I was doing the similar thing and experienced the same issue. The solution is to use audioContext.createJavaScriptNode().

instead of playing chunk upon receiving it from websocket, you should store it somewhere and fill the output buffer when requested.

==

var audioNode = audioContext.createJavaScriptNode(4096, 1, 1);

audioNode.onaudioprocess = function(event) {
                           if (decodedAudioBuffer.length >= bufferSize) {
                                  var decoded = decodedAudioBuffer.splice(0, bufferSize);
                                  var samples = new Float32Array(bufferSize);
                                  for (var i=0; i<decoded.length; i++) {
                                         samples[i] = decoded[i];
                                  }

                                  samples = resampler.resample(samples);
                                  var output = event.outputBuffer.getChannelData(0);
                                    for (var i = 0; i < output.length; i++) {
                                      output[i] = samples[i];
                                    }

                           }
};

audioNode.connect(audioContext.destination);
0
votes

Think the issue is that you're scheduling based on audioContext.currentTime plus total buffer duration. But the problem is that currentTime is a moving target. You need a consistent anchor to base all of your timing on.

What you probably want to do is, the first time you start playing, say var AudioStart = audioContext.currentTime and then keep adding to it the way you already are. Then just schedule your playback as source.start(AudioStart);.