-
-
Save meziantou/edb7217fddfbb70e899e to your computer and use it in GitHub Desktop.
<!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> |
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.
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?
@miketheappdev
Set sampleRate to 48000 and you'll get the normal pitch.
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 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();
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
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,
How to reverse the procedure to get the samples as
Float32Array
from wav file?
wav.slice(44)
, https://stackoverflow.com/a/35248852
Hi guys, can any one tell me how to send the blob or the arrayBuffer to the blazor ?
Hi , I tried same approach as mentioned above with multichannel
var buffer = [];
for (var channel = 0; channel < _2 ; channel++) {
buffer.push(e.inputBuffer.getChannelData(channel));
}
I am using single audio device and expecting byte data for each channels - buffer[0] = [0,1,2,3,2] , buffer[1] = [0,0,0,0,0]
but receive similar data from both channels - eg (channel 1) buffer[0]= [0,1,2,3,2] , (channel 2) buffer[1] = [0,1,2,3,2]
please help me , how to get the buffer based on channels.
Thanks
Pradeep
hello, this has been very helpful.
One feedback: while using it, I noticed that the microphone is not closed unless you add on top of what you wrote:
// stop recording
recorder.disconnect(context.destination);
mediaStream.disconnect(recorder);
also:
mediaStream.getAudioTracks().forEach(track => {
track.stop();
})
context.close();
I hope this could be my small contribution back :)
How to reverse the procedure to get the samples as
Float32Array
fromwav
file?