Last active
January 29, 2022 14:01
-
-
Save revolunet/46d4187c3b6f28632a91421c1f2a9fad to your computer and use it in GitHub Desktop.
web audio + wav buffering
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Goal : instantly play any wav source without download the file and without <audio/> | |
Idea is to use the fetch streaming API and pass raw data to web audio | |
My use case is playng a wav file | |
following http://stackoverflow.com/questions/38589614/webaudio-streaming-with-fetch-domexception-unable-to-decode-audio-data/38593356#38593356 | |
you can try this example here : http://www.ee.columbia.edu/~dpwe/sounds/music/ | |
(just paste the code in the chrome console) | |
Problem : dirty sound | |
*/ | |
// from https://gist.github.com/asanoboy/3979747 | |
class Wav { | |
constructor(opt_params) { | |
this._sampleRate = opt_params && opt_params.sampleRate ? opt_params.sampleRate : 44100; | |
this._channels = opt_params && opt_params.channels ? opt_params.channels : 2; | |
this._eof = true; | |
this._bufferNeedle = 0; | |
this._buffer; | |
} | |
setBuffer(buffer) { | |
this._buffer = this.getWavInt16Array(buffer); | |
this._bufferNeedle = 0; | |
this._internalBuffer = ''; | |
this._hasOutputHeader = false; | |
this._eof = false; | |
} | |
getBuffer(len) { | |
var rt; | |
if( this._bufferNeedle + len >= this._buffer.length ){ | |
rt = new Int16Array(this._buffer.length - this._bufferNeedle); | |
this._eof = true; | |
} | |
else { | |
rt = new Int16Array(len); | |
} | |
for(var i=0; i<rt.length; i++){ | |
rt[i] = this._buffer[i+this._bufferNeedle]; | |
} | |
this._bufferNeedle += rt.length; | |
return rt.buffer; | |
} | |
eof() { | |
return this._eof; | |
} | |
getWavInt16Array(buffer) { | |
var intBuffer = new Int16Array(buffer.length + 23), tmp; | |
intBuffer[0] = 0x4952; // "RI" | |
intBuffer[1] = 0x4646; // "FF" | |
intBuffer[2] = (2*buffer.length + 15) & 0x0000ffff; // RIFF size | |
intBuffer[3] = ((2*buffer.length + 15) & 0xffff0000) >> 16; // RIFF size | |
intBuffer[4] = 0x4157; // "WA" | |
intBuffer[5] = 0x4556; // "VE" | |
intBuffer[6] = 0x6d66; // "fm" | |
intBuffer[7] = 0x2074; // "t " | |
intBuffer[8] = 0x0012; // fmt chunksize: 18 | |
intBuffer[9] = 0x0000; // | |
intBuffer[10] = 0x0001; // format tag : 1 | |
intBuffer[11] = this._channels; // channels: 2 | |
intBuffer[12] = this._sampleRate & 0x0000ffff; // sample per sec | |
intBuffer[13] = (this._sampleRate & 0xffff0000) >> 16; // sample per sec | |
intBuffer[14] = (2*this._channels*this._sampleRate) & 0x0000ffff; // byte per sec | |
intBuffer[15] = ((2*this._channels*this._sampleRate) & 0xffff0000) >> 16; // byte per sec | |
intBuffer[16] = 0x0004; // block align | |
intBuffer[17] = 0x0010; // bit per sample | |
intBuffer[18] = 0x0000; // cb size | |
intBuffer[19] = 0x6164; // "da" | |
intBuffer[20] = 0x6174; // "ta" | |
intBuffer[21] = (2*buffer.length) & 0x0000ffff; // data size[byte] | |
intBuffer[22] = ((2*buffer.length) & 0xffff0000) >> 16; // data size[byte] | |
for (var i = 0; i < buffer.length; i++) { | |
tmp = buffer[i]; | |
if (tmp >= 1) { | |
intBuffer[i+23] = (1 << 15) - 1; | |
} | |
else if (tmp <= -1) { | |
intBuffer[i+23] = -(1 << 15); | |
} | |
else { | |
intBuffer[i+23] = Math.round(tmp * (1 << 15)); | |
} | |
} | |
return intBuffer; | |
} | |
} | |
// factory | |
function createWavFromBuffer(buffer, sampleRate) { | |
var wav = new Wav({ | |
sampleRate: sampleRate, | |
channels: 1 | |
}); | |
wav.setBuffer(buffer); | |
return wav; | |
} | |
// ArrayBuffer -> Float32Array | |
var convertBlock = function(buffer) { | |
var incomingData = new Uint8Array(buffer); | |
var i, l = incomingData.length; | |
var outputData = new Float32Array(incomingData.length); | |
for (i = 0; i < l; i++) { | |
outputData[i] = (incomingData[i] - 128) / 128.0; | |
} | |
return outputData; | |
} | |
// function createLinkFromBlob(b) { | |
// var URLObject = window.webkitURL || window.URL; | |
// var url = URLObject.createObjectURL(b); | |
// // console.log(url); | |
// var link = document.createElement('a'); | |
// link.href = url | |
// link.textContent = 'play'; | |
// document.body.appendChild(link); | |
// } | |
// Wav -> blob | |
function getBlobFromWav(wav) { | |
var srclist = []; | |
while( !wav.eof() ){ | |
srclist.push(wav.getBuffer(1000)); | |
} | |
return new Blob(srclist, {type:'audio/wav'}); | |
} | |
// adapted from http://stackoverflow.com/questions/20475982/choppy-inaudible-playback-with-chunked-audio-through-web-audio-api | |
function play(url) { | |
var context = new (window.AudioContext || window.webkitAudioContext)(); | |
var audioStack = []; | |
// fetch API + streaming | |
// see https://jakearchibald.com/2015/thats-so-fetch/ | |
fetch(url).then(function(response) { | |
var reader = response.body.getReader(); | |
function read() { | |
// on each received chunk | |
return reader.read().then(({ value, done })=> { | |
if (value && value.buffer) { | |
// create a new .wav with the chunk | |
let wav = createWavFromBuffer(convertBlock(value.buffer), 44100); | |
// transform to blob | |
let blob = getBlobFromWav(wav); | |
// read blob and send to audio | |
var reader = new FileReader(); | |
reader.onloadend = function() { | |
context.decodeAudioData(reader.result, function(buf) { | |
audioStack.push(buf); | |
if (audioStack.length) { | |
scheduleBuffers(); | |
} | |
}); | |
}; | |
reader.readAsArrayBuffer(blob); | |
} | |
if (done) { | |
console.log('done'); | |
return; | |
} | |
// read next | |
read() | |
}); | |
} | |
// start reading | |
read(); | |
}) | |
var nextTime = 0; | |
function scheduleBuffers() { | |
while ( audioStack.length) { | |
var buffer = audioStack.shift(); | |
var source = context.createBufferSource(); | |
source.buffer = buffer; | |
source.connect(context.destination); | |
if (nextTime == 0) | |
nextTime = context.currentTime + 0.1; /// add 50ms latency to work well across systems - tune this if you like | |
source.start(nextTime); | |
nextTime += source.buffer.duration; // Make the next buffer wait the length of the last buffer before being played | |
}; | |
} | |
} | |
// try http://www.ee.columbia.edu/~dpwe/sounds/music/ | |
play('http://www.ee.columbia.edu/~dpwe/sounds/music/africa-toto.wav') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi,
I'm developing a project based on an Android app and a web server. The Android app sends a real time stream of byte arrays that contains the data of PCM audio encoded. The server receive that stream and a possible web client should listen the audio that is recording the Android app. I was understanding your code and I have tested, adapting it to my code. I have been able to reproduce the audio stream, but there are a lot of noise that makes difficult listen the audio.
Do you have any suggestions to remove that noise?
Thanks!