Skip to content

Commit

Permalink
fixed muaz-khan#261 added "simple-demos/raw-pcm.html" demo.
Browse files Browse the repository at this point in the history
  • Loading branch information
muaz-khan committed Jun 2, 2017
1 parent 56e466b commit 48d837b
Show file tree
Hide file tree
Showing 5 changed files with 342 additions and 4 deletions.
1 change: 1 addition & 0 deletions RecordRTC-to-Nodejs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/*
12 changes: 11 additions & 1 deletion RecordRTC.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

// Last time updated: 2017-06-01 1:22:09 PM UTC
// Last time updated: 2017-06-02 6:55:15 AM UTC

// ________________
// RecordRTC v5.4.1
Expand Down Expand Up @@ -2811,12 +2811,22 @@ function StereoAudioRecorder(mediaStream, config) {
}

recordingLength += bufferSize;

// export raw PCM
self.recordingLength = recordingLength;
}

jsAudioNode.onaudioprocess = onAudioProcessDataAvailable;

// to prevent self audio to be connected with speakers
jsAudioNode.connect(context.destination);

// export raw PCM
this.leftchannel = leftchannel;
this.rightchannel = rightchannel;
this.numberOfAudioChannels = numberOfAudioChannels;
this.desiredSampRate = desiredSampRate;
this.sampleRate = sampleRate;
}

if (typeof RecordRTC !== 'undefined') {
Expand Down
6 changes: 3 additions & 3 deletions RecordRTC.min.js

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions dev/StereoAudioRecorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -585,12 +585,22 @@ function StereoAudioRecorder(mediaStream, config) {
}

recordingLength += bufferSize;

// export raw PCM
self.recordingLength = recordingLength;
}

jsAudioNode.onaudioprocess = onAudioProcessDataAvailable;

// to prevent self audio to be connected with speakers
jsAudioNode.connect(context.destination);

// export raw PCM
this.leftchannel = leftchannel;
this.rightchannel = rightchannel;
this.numberOfAudioChannels = numberOfAudioChannels;
this.desiredSampRate = desiredSampRate;
this.sampleRate = sampleRate;
}

if (typeof RecordRTC !== 'undefined') {
Expand Down
317 changes: 317 additions & 0 deletions simple-demos/raw-pcm.html
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>

0 comments on commit 48d837b

Please sign in to comment.