Created
October 29, 2019 16:49
-
-
Save reinhart1010/d9f16556cfb2fac8bd357869555e76d1 to your computer and use it in GitHub Desktop.
Record audio stream via Web Audio API
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
<!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