Record audio with JavaScript
Browsers provide more and more APIs which allows creating applications with more and more features. In this post, we will see how to record the sound of the microphone.
The first thing is to ask the user's consent to use the microphone:
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
navigator.getUserMedia({
audio: true
},
function (e) {
// success
},
function (e) {
// error
console.error(e);
});
This should display this popup:
Once accepted, you can create the code to read the audio stream:
navigator.getUserMedia({ audio: true },
function (e) {
// creates the audio context
window.AudioContext = window.AudioContext || window.webkitAudioContext;
context = new AudioContext();
// creates an audio node from the microphone incoming stream
mediaStream = context.createMediaStreamSource(e);
// https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createScriptProcessor
var bufferSize = 2048;
var numberOfInputChannels = 2;
var numberOfOutputChannels = 2;
if (context.createScriptProcessor) {
recorder = context.createScriptProcessor(bufferSize, numberOfInputChannels, numberOfOutputChannels);
} else {
recorder = context.createJavaScriptNode(bufferSize, numberOfInputChannels, numberOfOutputChannels);
}
recorder.onaudioprocess = function (e) {
console.log("on audio progress");
}
// we connect the recorder with the input stream
mediaStream.connect(recorder);
recorder.connect(context.destination);
}
In the onaudioprogress
event you can store the read content in an array:
var leftchannel = [];
var rightchannel = [];
var recordingLength = 0;
recorder.onaudioprocess = function (e) {
leftchannel.push(new Float32Array(e.inputBuffer.getChannelData(0)));
rightchannel.push(new Float32Array(e.inputBuffer.getChannelData(1)));
recordingLength += bufferSize;
}
Once the recording is finished (for example by clicking on a button), you can create a WAV audio file. This format is very simple hence the choice. It consists of a header followed by data.
// stop recording
recorder.disconnect(context.destination);
mediaStream.disconnect(recorder);
// we flat the left and right channels down
// Float32Array[] => Float32Array
var leftBuffer = flattenArray(leftchannel, recordingLength); // flattenArray is on GitHub (see below)
var rightBuffer = flattenArray(rightchannel, recordingLength);
// we interleave both channels together
// [left[0],right[0],left[1],right[1],...]
var interleaved = interleave(leftBuffer, rightBuffer); // interleave is on GitHub (see below)
// we create our wav file
var buffer = new ArrayBuffer(44 + interleaved.length * 2);
var view = new DataView(buffer);
// RIFF chunk descriptor
writeUTFBytes(view, 0, 'RIFF');
view.setUint32(4, 44 + interleaved.length * 2, true);
writeUTFBytes(view, 8, 'WAVE');
// FMT sub-chunk
writeUTFBytes(view, 12, 'fmt ');
view.setUint32(16, 16, true); // chunkSize
view.setUint16(20, 1, true); // wFormatTag
view.setUint16(22, 2, true); // wChannels: stereo (2 channels)
view.setUint32(24, sampleRate, true); // dwSamplesPerSec
view.setUint32(28, sampleRate * 4, true); // dwAvgBytesPerSec
view.setUint16(32, 4, true); // wBlockAlign
view.setUint16(34, 16, true); // wBitsPerSample
// data sub-chunk
writeUTFBytes(view, 36, 'data');
view.setUint32(40, interleaved.length * 2, true);
// write the PCM samples
var index = 44;
var volume = 1;
for (var i = 0; i < interleaved.length; i++) {
view.setInt16(index, interleaved[i] * (0x7FFF * volume), true);
index += 2;
}
// our final blob
var blob = new Blob([view], { type: 'audio/wav' });
There you go. It is then possible to play the sound directly in the browser:
var url = window.URL.createObjectURL(blob);
var audio = new Audio(url);
audio.play();
Or download the file:
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = "sample.wav";
a.click();
window.URL.revokeObjectURL(url);
You can find the working example on GitHub: https://gist.github.com/meziantou/edb7217fddfbb70e899e.
Do you have a question or a suggestion about this post? Contact me!