Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Javascript - Record audio
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<h1>Audio</h1>
<button id="startRecordingButton">Start recording</button>
<button id="stopRecordingButton">Stop recording</button>
<button id="playButton">Play</button>
<button id="downloadButton">Download</button>
<script>
var startRecordingButton = document.getElementById("startRecordingButton");
var stopRecordingButton = document.getElementById("stopRecordingButton");
var playButton = document.getElementById("playButton");
var downloadButton = document.getElementById("downloadButton");
var leftchannel = [];
var rightchannel = [];
var recorder = null;
var recordingLength = 0;
var volume = null;
var mediaStream = null;
var sampleRate = 44100;
var context = null;
var blob = null;
startRecordingButton.addEventListener("click", function () {
// Initialize recorder
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
navigator.getUserMedia(
{
audio: true
},
function (e) {
console.log("user consent");
// 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
// bufferSize: the onaudioprocess event is called when the buffer is full
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) {
leftchannel.push(new Float32Array(e.inputBuffer.getChannelData(0)));
rightchannel.push(new Float32Array(e.inputBuffer.getChannelData(1)));
recordingLength += bufferSize;
}
// we connect the recorder
mediaStream.connect(recorder);
recorder.connect(context.destination);
},
function (e) {
console.error(e);
});
});
stopRecordingButton.addEventListener("click", function () {
// stop recording
recorder.disconnect(context.destination);
mediaStream.disconnect(recorder);
// we flat the left and right channels down
// Float32Array[] => Float32Array
var leftBuffer = flattenArray(leftchannel, recordingLength);
var rightBuffer = flattenArray(rightchannel, recordingLength);
// we interleave both channels together
// [left[0],right[0],left[1],right[1],...]
var interleaved = interleave(leftBuffer, rightBuffer);
// 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
blob = new Blob([view], { type: 'audio/wav' });
});
playButton.addEventListener("click", function () {
if (blob == null) {
return;
}
var url = window.URL.createObjectURL(blob);
var audio = new Audio(url);
audio.play();
});
downloadButton.addEventListener("click", function () {
if (blob == null) {
return;
}
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);
});
function flattenArray(channelBuffer, recordingLength) {
var result = new Float32Array(recordingLength);
var offset = 0;
for (var i = 0; i < channelBuffer.length; i++) {
var buffer = channelBuffer[i];
result.set(buffer, offset);
offset += buffer.length;
}
return result;
}
function interleave(leftChannel, rightChannel) {
var length = leftChannel.length + rightChannel.length;
var result = new Float32Array(length);
var inputIndex = 0;
for (var index = 0; index < length;) {
result[index++] = leftChannel[inputIndex];
result[index++] = rightChannel[inputIndex];
inputIndex++;
}
return result;
}
function writeUTFBytes(view, offset, string) {
for (var i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
</script>
</body>
</html>
@iamzhanghao

This comment has been minimized.

Copy link

iamzhanghao commented Jun 11, 2018

Hi Can you advice on how to record Mono channel PCM 16000Hz? Thanks!

@chriss5

This comment has been minimized.

Copy link

chriss5 commented Apr 5, 2019

@iamzhanghao: If my research is correct, you cannot influence the sampling rate but have to work with the sampling rate the browser offers you. [Update]: You can resample an AudioBuffer using OfflineAudioContext. Example see here: https://stackoverflow.com/questions/27598270/resample-audio-buffer-from-44100-to-16000 [/Update]

Regarding mono vs. stereo: Assuming that you receive similar data from both, left and right channel (or know on which channel you want to record) you just have to make some changes to the riff header. If I'm not mistaken it should be enough to change wChannels and dwAvgBytesPerSec:

view.setUint16(22, 1, true); // wChannels: mono (1channel) / stereo (2 channels)
view.setUint32(24, sampleRate, true); // dwSamplesPerSec
view.setUint32(28, sampleRate * 2, true); // dwAvgBytesPerSec sampleRate  *2 for 16 bit mono / sampleRate *4 for 16 bit stereo

then remove the interleaving. It's the line :
var interleaved = interleave(leftBuffer, rightBuffer);
and replace all remaining occurrences of interleaved with leftBuffer or rightBuffer to set the right buffer size and write the left or right buffer.

@crky14

This comment has been minimized.

Copy link

crky14 commented Nov 3, 2019

Hey, great work. Can I ask why are you multiplying by 0x7FFF when writing PCM data ? Media stream returns 32Float in range from -1 to 1 if Im correct. To convert to 16 bit PCM you should use this formula if Im correct sample < 0 ? sample * 0x8000 : sample * 0x7FFF.
If Im wrong please feel free to correct me. Thanks !

@DoeJon2047

This comment has been minimized.

Copy link

DoeJon2047 commented Dec 12, 2019

How can i have the final blob of wav,mono,16bit,16Khz,PCM audio?

@guest271314

This comment has been minimized.

Copy link

guest271314 commented Jan 10, 2020

How to reverse the procedure to get the samples as Float32Array from wav file?

@priyadharshini-sent

This comment has been minimized.

Copy link

priyadharshini-sent commented Jun 23, 2020

Hi,

Can you pls give sample for record Mono channel and 16000Hz.

I've done this part

view.setUint16(22, 1, true); // wChannels: mono (1channel) / stereo (2 channels)
view.setUint32(24, sampleRate, true); // dwSamplesPerSec
view.setUint32(28, sampleRate * 2, true); // dwAvgBytesPerSec sampleRate *2 for 16 bit mono / sampleRate *4 for 16 bit stereo

but after the changes its not working.

@miketheappdev

This comment has been minimized.

Copy link

miketheappdev commented Jul 20, 2020

Hello, this has worked great for me in my current project, however I've encountered issues where the recorded audio gives the voice being recorded a deeper tone. Has anyone else run into this?

@dmitrysmagin

This comment has been minimized.

Copy link

dmitrysmagin commented Aug 7, 2020

@miketheappdev
Set sampleRate to 48000 and you'll get the normal pitch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.