Skip to content

Instantly share code, notes, and snippets.

@reinhart1010
Created October 29, 2019 16:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reinhart1010/d9f16556cfb2fac8bd357869555e76d1 to your computer and use it in GitHub Desktop.
Save reinhart1010/d9f16556cfb2fac8bd357869555e76d1 to your computer and use it in GitHub Desktop.
Record audio stream via Web Audio API
<!DOCTYPE html>
<html>
<body>
<button onclick="record({audio: true, video: false})">Record</button>
<button onclick="stop()">Stop</button>
</body>
<script>
let timeInterval = 3000; // in milliseconds
// From https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
let recording;
let sampleRate;
let requesttimer;
async function record(constraints) {
let stream = null;
recording = true;
try {
stream = await navigator.mediaDevices.getUserMedia(constraints);
console.log(stream);
// From https://developers.google.com/web/fundamentals/media/recording-audio
const context = new (window.AudioContext || window.webkitAudioContext)();
const source = context.createMediaStreamSource(stream);
const processor = context.createScriptProcessor(1024, 1, 1);
source.connect(processor);
processor.connect(context.destination);
requesttimer = setInterval(exportBuffer, timeInterval); // From https://www.w3schools.com/jsref/met_win_setinterval.asp
processor.onaudioprocess = function(e) {
// From https://aws.amazon.com/blogs/machine-learning/capturing-voice-input-in-a-browser/
if (!recording) {
exportBuffer();
clearInterval(requesttimer); // From https://www.w3schools.com/jsref/met_win_setinterval.asp
context.close(); // From https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/close
}
// Do something with the data, e.g. convert it to WAV
sampleRate = e.inputBuffer.sampleRate;
record2(e.inputBuffer.getChannelData(0)); // From https://aws.amazon.com/blogs/machine-learning/capturing-voice-input-in-a-browser/
};
} catch(err) {
/* handle the error */
}
}
// From https://aws.amazon.com/blogs/machine-learning/capturing-voice-input-in-a-browser/
function stop(){
recording = false;
}
var recLength = 0,
recBuffer = [];
function record2(inputBuffer) {
recBuffer.push(inputBuffer[0]);
recLength += inputBuffer[0].length;
}
function exportBuffer() {
// Merge
var mergedBuffers = mergeBuffers(recBuffer, recLength);
// Downsample
var downsampledBuffer = downsampleBuffer(mergedBuffers, 16000);
// Encode as a WAV
var encodedWav = encodeWAV(downsampledBuffer);
// Create Blob
var audioBlob = new Blob([encodedWav], { type: 'application/octet-stream' });
postMessage(audioBlob);
}
// From https://aws.amazon.com/blogs/machine-learning/capturing-voice-input-in-a-browser/
// and https://github.com/mattdiamond/Recorderjs/
function mergeBuffers(bufferArray, recLength) {
var result = new Float32Array(recLength);
var offset = 0;
for (var i = 0; i < bufferArray.length; i++) {
result.set(bufferArray[i], offset);
offset += bufferArray[i].length;
}
return result;
}
function downsampleBuffer(buffer) {
if (16000 === sampleRate) {
return buffer;
}
var sampleRateRatio = sampleRate / 16000;
var newLength = Math.round(buffer.length / sampleRateRatio);
var result = new Float32Array(newLength);
var offsetResult = 0;
var offsetBuffer = 0;
while (offsetResult < result.length) {
var nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
var accum = 0,
count = 0;
for (var i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
accum += buffer[i];
count++;
}
result[offsetResult] = accum / count;
offsetResult++;
offsetBuffer = nextOffsetBuffer;
}
return result;
}
function writeString(view, offset, string) {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
function floatTo16BitPCM(output, offset, input) {
for (let i = 0; i < input.length; i++, offset += 2) {
let s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}
function encodeWAV(samples) {
var buffer = new ArrayBuffer(44 + samples.length * 2);
var view = new DataView(buffer);
writeString(view, 0, 'RIFF');
view.setUint32(4, 32 + samples.length * 2, true);
writeString(view, 8, 'WAVE');
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, 1, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * 2, true);
view.setUint16(32, 2, true);
view.setUint16(34, 16, true);
writeString(view, 36, 'data');
view.setUint32(40, samples.length * 2, true);
floatTo16BitPCM(view, 44, samples);
return view;
}
// TODO: POST via XMLHttpRequest
function postMessage(data){
console.log(data);
recLength = 0;
recBuffer = [];
}
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment