forked from muaz-khan/RecordRTC
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fixed muaz-khan#261 added "simple-demos/raw-pcm.html" demo.
- Loading branch information
Showing
5 changed files
with
342 additions
and
4 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules/* |
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
Large diffs are not rendered by default.
Oops, something went wrong.
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,317 @@ | ||
<style> | ||
html, body { | ||
margin: 0!important; | ||
padding: 0!important; | ||
overflow: hidden!important; | ||
width: 100%; | ||
} | ||
</style> | ||
|
||
<title>Process RAW PCM data using RecordRTC</title> | ||
<h1>Process RAW PCM data using RecordRTC</h1> | ||
|
||
<br> | ||
<button id="btn-start-recording">Start Recording</button> | ||
<button id="btn-stop-recording" disabled>Stop Recording</button> | ||
|
||
<hr> | ||
<audio controls autoplay></audio> | ||
|
||
<script src="/RecordRTC.js"></script> | ||
<!-- for Edge/FF/Chrome/Opera/etc. getUserMedia support --> | ||
<script src="https://cdn.webrtc-experiment.com/gumadapter.js"></script> | ||
<script> | ||
var audio = document.querySelector('audio'); | ||
|
||
function captureMicrophone(callback) { | ||
navigator.mediaDevices.getUserMedia({ | ||
audio: true | ||
}).then(function(microphone) { | ||
callback(microphone); | ||
}).catch(function(error) { | ||
alert('Unable to capture your microphone. Please check console logs.'); | ||
console.error(error); | ||
}); | ||
} | ||
|
||
function stopRecordingCallback() { | ||
// ------------------------------------------------------------ | ||
// get access to StereoAudioRecorder object (name as "internal-recorder") | ||
// ------------------------------------------------------------ | ||
var internalRecorder = recorder.getInternalRecorder(); | ||
|
||
// ------------------------------------------------------------ | ||
// get left and right audio channels | ||
// ------------------------------------------------------------ | ||
var leftchannel = internalRecorder.leftchannel; | ||
var rightchannel = internalRecorder.rightchannel; | ||
|
||
// ------------------------------------------------------------ | ||
// create your own WAV | ||
// ------------------------------------------------------------ | ||
mergeLeftRightBuffers({ | ||
desiredSampRate: internalRecorder.desiredSampRate, | ||
sampleRate: internalRecorder.sampleRate, | ||
numberOfAudioChannels: internalRecorder.numberOfAudioChannels, | ||
internalInterleavedLength: internalRecorder.recordingLength, | ||
leftBuffers: leftchannel, | ||
rightBuffers: internalRecorder.numberOfAudioChannels === 1 ? [] : rightchannel | ||
}, function(buffer, view) { | ||
// ------------------------------------------------------------ | ||
// here is your own WAV (generated by your own codes) | ||
// ------------------------------------------------------------ | ||
var blob = new Blob([buffer], { | ||
type: 'audio/wav' | ||
}); | ||
audio.src = URL.createObjectURL(blob); | ||
audio.play(); | ||
}); | ||
|
||
recorder.microphone.stop(); | ||
} | ||
|
||
var recorder; // globally accessible | ||
|
||
document.getElementById('btn-start-recording').onclick = function() { | ||
this.disabled = true; | ||
captureMicrophone(function(microphone) { | ||
audio.src = URL.createObjectURL(microphone); | ||
audio.play(); | ||
|
||
recorder = RecordRTC(microphone, { | ||
type: 'audio', | ||
recorderType: StereoAudioRecorder, | ||
// numberOfAudioChannels: 1 | ||
}); | ||
|
||
recorder.startRecording(); | ||
|
||
// release microphone on stopRecording | ||
recorder.microphone = microphone; | ||
|
||
document.getElementById('btn-stop-recording').disabled = false; | ||
}); | ||
}; | ||
|
||
document.getElementById('btn-stop-recording').onclick = function() { | ||
this.disabled = true; | ||
recorder.stopRecording(stopRecordingCallback); | ||
}; | ||
|
||
// ------------------------------------------------------------ | ||
|
||
// below two methods are copied from dev/StereoAudioRecorder.js | ||
// just to explain how to process RAW PCM data | ||
|
||
// ------------------------------------------------------------ | ||
|
||
function mergeLeftRightBuffers(config, callback) { | ||
function mergeAudioBuffers(config, cb) { | ||
var numberOfAudioChannels = config.numberOfAudioChannels; | ||
|
||
// todo: "slice(0)" --- is it causes loop? Should be removed? | ||
var leftBuffers = config.leftBuffers.slice(0); | ||
var rightBuffers = config.rightBuffers.slice(0); | ||
var sampleRate = config.sampleRate; | ||
var internalInterleavedLength = config.internalInterleavedLength; | ||
var desiredSampRate = config.desiredSampRate; | ||
|
||
if (numberOfAudioChannels === 2) { | ||
leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength); | ||
rightBuffers = mergeBuffers(rightBuffers, internalInterleavedLength); | ||
if (desiredSampRate) { | ||
leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate); | ||
rightBuffers = interpolateArray(rightBuffers, desiredSampRate, sampleRate); | ||
} | ||
} | ||
|
||
if (numberOfAudioChannels === 1) { | ||
leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength); | ||
if (desiredSampRate) { | ||
leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate); | ||
} | ||
} | ||
|
||
// set sample rate as desired sample rate | ||
if (desiredSampRate) { | ||
sampleRate = desiredSampRate; | ||
} | ||
|
||
// for changing the sampling rate, reference: | ||
// http://stackoverflow.com/a/28977136/552182 | ||
function interpolateArray(data, newSampleRate, oldSampleRate) { | ||
var fitCount = Math.round(data.length * (newSampleRate / oldSampleRate)); | ||
//var newData = new Array(); | ||
var newData = []; | ||
//var springFactor = new Number((data.length - 1) / (fitCount - 1)); | ||
var springFactor = Number((data.length - 1) / (fitCount - 1)); | ||
newData[0] = data[0]; // for new allocation | ||
for (var i = 1; i < fitCount - 1; i++) { | ||
var tmp = i * springFactor; | ||
//var before = new Number(Math.floor(tmp)).toFixed(); | ||
//var after = new Number(Math.ceil(tmp)).toFixed(); | ||
var before = Number(Math.floor(tmp)).toFixed(); | ||
var after = Number(Math.ceil(tmp)).toFixed(); | ||
var atPoint = tmp - before; | ||
newData[i] = linearInterpolate(data[before], data[after], atPoint); | ||
} | ||
newData[fitCount - 1] = data[data.length - 1]; // for new allocation | ||
return newData; | ||
} | ||
|
||
function linearInterpolate(before, after, atPoint) { | ||
return before + (after - before) * atPoint; | ||
} | ||
|
||
function mergeBuffers(channelBuffer, rLength) { | ||
var result = new Float64Array(rLength); | ||
var offset = 0; | ||
var lng = channelBuffer.length; | ||
|
||
for (var i = 0; i < lng; 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 Float64Array(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) { | ||
var lng = string.length; | ||
for (var i = 0; i < lng; i++) { | ||
view.setUint8(offset + i, string.charCodeAt(i)); | ||
} | ||
} | ||
|
||
// interleave both channels together | ||
var interleaved; | ||
|
||
if (numberOfAudioChannels === 2) { | ||
interleaved = interleave(leftBuffers, rightBuffers); | ||
} | ||
|
||
if (numberOfAudioChannels === 1) { | ||
interleaved = leftBuffers; | ||
} | ||
|
||
var interleavedLength = interleaved.length; | ||
|
||
// create wav file | ||
var resultingBufferLength = 44 + interleavedLength * 2; | ||
|
||
var buffer = new ArrayBuffer(resultingBufferLength); | ||
|
||
var view = new DataView(buffer); | ||
|
||
// RIFF chunk descriptor/identifier | ||
writeUTFBytes(view, 0, 'RIFF'); | ||
|
||
// RIFF chunk length | ||
view.setUint32(4, 44 + interleavedLength * 2, true); | ||
|
||
// RIFF type | ||
writeUTFBytes(view, 8, 'WAVE'); | ||
|
||
// format chunk identifier | ||
// FMT sub-chunk | ||
writeUTFBytes(view, 12, 'fmt '); | ||
|
||
// format chunk length | ||
view.setUint32(16, 16, true); | ||
|
||
// sample format (raw) | ||
view.setUint16(20, 1, true); | ||
|
||
// stereo (2 channels) | ||
view.setUint16(22, numberOfAudioChannels, true); | ||
|
||
// sample rate | ||
view.setUint32(24, sampleRate, true); | ||
|
||
// byte rate (sample rate * block align) | ||
view.setUint32(28, sampleRate * 2, true); | ||
|
||
// block align (channel count * bytes per sample) | ||
view.setUint16(32, numberOfAudioChannels * 2, true); | ||
|
||
// bits per sample | ||
view.setUint16(34, 16, true); | ||
|
||
// data sub-chunk | ||
// data chunk identifier | ||
writeUTFBytes(view, 36, 'data'); | ||
|
||
// data chunk length | ||
view.setUint32(40, interleavedLength * 2, true); | ||
|
||
// write the PCM samples | ||
var lng = interleavedLength; | ||
var index = 44; | ||
var volume = 1; | ||
for (var i = 0; i < lng; i++) { | ||
view.setInt16(index, interleaved[i] * (0x7FFF * volume), true); | ||
index += 2; | ||
} | ||
|
||
if (cb) { | ||
return cb({ | ||
buffer: buffer, | ||
view: view | ||
}); | ||
} | ||
|
||
postMessage({ | ||
buffer: buffer, | ||
view: view | ||
}); | ||
} | ||
|
||
if (!isChrome) { | ||
// its Microsoft Edge | ||
mergeAudioBuffers(config, function(data) { | ||
callback(data.buffer, data.view); | ||
}); | ||
return; | ||
} | ||
|
||
|
||
var webWorker = processInWebWorker(mergeAudioBuffers); | ||
|
||
webWorker.onmessage = function(event) { | ||
callback(event.data.buffer, event.data.view); | ||
|
||
// release memory | ||
URL.revokeObjectURL(webWorker.workerURL); | ||
}; | ||
|
||
webWorker.postMessage(config); | ||
} | ||
|
||
function processInWebWorker(_function) { | ||
var workerURL = URL.createObjectURL(new Blob([_function.toString(), | ||
';this.onmessage = function (e) {' + _function.name + '(e.data);}' | ||
], { | ||
type: 'application/javascript' | ||
})); | ||
|
||
var worker = new Worker(workerURL); | ||
worker.workerURL = workerURL; | ||
return worker; | ||
} | ||
</script> |