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

@mikethedevdad

This comment has been minimized.

Copy link

@mikethedevdad mikethedevdad 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 dmitrysmagin commented Aug 7, 2020

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

@Anders-Lunde

This comment has been minimized.

Copy link

@Anders-Lunde Anders-Lunde commented Oct 2, 2020

I can get this to work on Chrome in my PC, but not on Safari on iPadOS 14.01. Any advice?

@guest271314

This comment has been minimized.

Copy link

@guest271314 guest271314 commented Oct 3, 2020

@Anders-Lunde Which portion of the code does not work? Does Safari support WAV file playback?

@Anders-Lunde

This comment has been minimized.

Copy link

@Anders-Lunde Anders-Lunde commented Oct 5, 2020

I can get this to work on Chrome in my PC, but not on Safari on iPadOS 14.01. Any advice?
@Anders-Lunde Which portion of the code does not work? Does Safari support WAV file playback?

I figured it out. It was related to AudioContext, in in my typescript project I had to do this:

const AudioContext = window.AudioContext || // Default
          (window as any).webkitAudioContext; // Safari and old versions of Chrome
const context = new AudioContext();
@prasanna-code

This comment has been minimized.

Copy link

@prasanna-code prasanna-code commented Oct 6, 2020

Hi Guys,

Can anyone help me to record the Wav audio in a 24-bit format? I achieved it in 16 and 32 bit.

Thanks in advance

Thanks,
PJ

@gauravgargtech

This comment has been minimized.

Copy link

@gauravgargtech gauravgargtech commented Oct 20, 2020

Thanks for sharing the cool snippet and can we check silence and automatically stop the recording and when a person starts to speak then restart the recording,

@guest271314

This comment has been minimized.

Copy link

@guest271314 guest271314 commented Oct 20, 2020

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

wav.slice(44), https://stackoverflow.com/a/35248852

@Bufarhan

This comment has been minimized.

Copy link

@Bufarhan Bufarhan commented Apr 2, 2021

Hi guys, can any one tell me how to send the blob or the arrayBuffer to the blazor ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment