mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-09 22:51:22 +00:00
Merge Lanczos changes for front end.
This commit is contained in:
@@ -124,7 +124,18 @@ Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
|||||||
* @constant
|
* @constant
|
||||||
* @type {Number}
|
* @type {Number}
|
||||||
*/
|
*/
|
||||||
var BUFFER_SIZE = 512;
|
var BUFFER_SIZE = 2048;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The window size to use when applying Lanczos interpolation, commonly
|
||||||
|
* denoted by the variable "a".
|
||||||
|
* See: https://en.wikipedia.org/wiki/Lanczos_resampling
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @contant
|
||||||
|
* @type Number
|
||||||
|
*/
|
||||||
|
var LANCZOS_WINDOW_SIZE = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The format of audio this recorder will encode.
|
* The format of audio this recorder will encode.
|
||||||
@@ -183,6 +194,133 @@ Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
|||||||
*/
|
*/
|
||||||
var maxSampleValue = (format.bytesPerSample === 1) ? 128 : 32768;
|
var maxSampleValue = (format.bytesPerSample === 1) ? 128 : 32768;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of audio samples read from the local audio input device
|
||||||
|
* over the life of this audio recorder.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
var readSamples = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of audio samples written to the underlying Guacamole
|
||||||
|
* connection over the life of this audio recorder.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
var writtenSamples = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source node providing access to the local audio input device.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {MediaStreamAudioSourceNode}
|
||||||
|
*/
|
||||||
|
var source = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The script processing node which receives audio input from the media
|
||||||
|
* stream source node as individual audio buffers.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {ScriptProcessorNode}
|
||||||
|
*/
|
||||||
|
var processor = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The normalized sinc function. The normalized sinc function is defined as
|
||||||
|
* 1 for x=0 and sin(PI * x) / (PI * x) for all other values of x.
|
||||||
|
*
|
||||||
|
* See: https://en.wikipedia.org/wiki/Sinc_function
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Number} x
|
||||||
|
* The point at which the normalized sinc function should be computed.
|
||||||
|
*
|
||||||
|
* @returns {Number}
|
||||||
|
* The value of the normalized sinc function at x.
|
||||||
|
*/
|
||||||
|
var sinc = function sinc(x) {
|
||||||
|
|
||||||
|
// The value of sinc(0) is defined as 1
|
||||||
|
if (x === 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Otherwise, normlized sinc(x) is sin(PI * x) / (PI * x)
|
||||||
|
var piX = Math.PI * x;
|
||||||
|
return Math.sin(piX) / piX;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the value of the Lanczos kernal at point x for a given window
|
||||||
|
* size. See: https://en.wikipedia.org/wiki/Lanczos_resampling
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Number} x
|
||||||
|
* The point at which the value of the Lanczos kernel should be
|
||||||
|
* computed.
|
||||||
|
*
|
||||||
|
* @param {Number} a
|
||||||
|
* The window size to use for the Lanczos kernel.
|
||||||
|
*
|
||||||
|
* @returns {Number}
|
||||||
|
* The value of the Lanczos kernel at the given point for the given
|
||||||
|
* window size.
|
||||||
|
*/
|
||||||
|
var lanczos = function lanczos(x, a) {
|
||||||
|
|
||||||
|
// Lanczos is sinc(x) * sinc(x / a) for -a < x < a ...
|
||||||
|
if (-a < x && x < a)
|
||||||
|
return sinc(x) * sinc(x / a);
|
||||||
|
|
||||||
|
// ... and 0 otherwise
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the value of the waveform represented by the audio data at
|
||||||
|
* the given location. If the value cannot be determined exactly as it does
|
||||||
|
* not correspond to an exact sample within the audio data, the value will
|
||||||
|
* be derived through interpolating nearby samples.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Float32Array} audioData
|
||||||
|
* An array of audio data, as returned by AudioBuffer.getChannelData().
|
||||||
|
*
|
||||||
|
* @param {Number} t
|
||||||
|
* The relative location within the waveform from which the value
|
||||||
|
* should be retrieved, represented as a floating point number between
|
||||||
|
* 0 and 1 inclusive, where 0 represents the earliest point in time and
|
||||||
|
* 1 represents the latest.
|
||||||
|
*
|
||||||
|
* @returns {Number}
|
||||||
|
* The value of the waveform at the given location.
|
||||||
|
*/
|
||||||
|
var interpolateSample = function getValueAt(audioData, t) {
|
||||||
|
|
||||||
|
// Convert [0, 1] range to [0, audioData.length - 1]
|
||||||
|
var index = (audioData.length - 1) * t;
|
||||||
|
|
||||||
|
// Determine the start and end points for the summation used by the
|
||||||
|
// Lanczos interpolation algorithm (see: https://en.wikipedia.org/wiki/Lanczos_resampling)
|
||||||
|
var start = Math.floor(index) - LANCZOS_WINDOW_SIZE + 1;
|
||||||
|
var end = Math.floor(index) + LANCZOS_WINDOW_SIZE;
|
||||||
|
|
||||||
|
// Calculate the value of the Lanczos interpolation function for the
|
||||||
|
// required range
|
||||||
|
var sum = 0;
|
||||||
|
for (var i = start; i <= end; i++) {
|
||||||
|
sum += (audioData[i] || 0) * lanczos(index - i, LANCZOS_WINDOW_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given AudioBuffer into an audio packet, ready for streaming
|
* Converts the given AudioBuffer into an audio packet, ready for streaming
|
||||||
* along the underlying output stream. Unlike the raw audio packets used by
|
* along the underlying output stream. Unlike the raw audio packets used by
|
||||||
@@ -200,9 +338,18 @@ Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
|||||||
*/
|
*/
|
||||||
var toSampleArray = function toSampleArray(audioBuffer) {
|
var toSampleArray = function toSampleArray(audioBuffer) {
|
||||||
|
|
||||||
// Calculate the number of samples in both input and output
|
// Track overall amount of data read
|
||||||
var inSamples = audioBuffer.length;
|
var inSamples = audioBuffer.length;
|
||||||
var outSamples = Math.floor(audioBuffer.duration * format.rate);
|
readSamples += inSamples;
|
||||||
|
|
||||||
|
// Calculate the total number of samples that should be written as of
|
||||||
|
// the audio data just received and adjust the size of the output
|
||||||
|
// packet accordingly
|
||||||
|
var expectedWrittenSamples = Math.round(readSamples * format.rate / audioBuffer.sampleRate);
|
||||||
|
var outSamples = expectedWrittenSamples - writtenSamples;
|
||||||
|
|
||||||
|
// Update number of samples written
|
||||||
|
writtenSamples += outSamples;
|
||||||
|
|
||||||
// Get array for raw PCM storage
|
// Get array for raw PCM storage
|
||||||
var data = new SampleArray(outSamples * format.channels);
|
var data = new SampleArray(outSamples * format.channels);
|
||||||
@@ -215,13 +362,8 @@ Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
|||||||
// Fill array with data from audio buffer channel
|
// Fill array with data from audio buffer channel
|
||||||
var offset = channel;
|
var offset = channel;
|
||||||
for (var i = 0; i < outSamples; i++) {
|
for (var i = 0; i < outSamples; i++) {
|
||||||
|
data[offset] = interpolateSample(audioData, i / (outSamples - 1)) * maxSampleValue;
|
||||||
// Apply naiive resampling
|
|
||||||
var inOffset = Math.floor(i / outSamples * inSamples);
|
|
||||||
data[offset] = Math.floor(audioData[inOffset] * maxSampleValue);
|
|
||||||
|
|
||||||
offset += format.channels;
|
offset += format.channels;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -235,15 +377,29 @@ Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
|||||||
|
|
||||||
// Abort stream if rejected
|
// Abort stream if rejected
|
||||||
if (status.code !== Guacamole.Status.Code.SUCCESS) {
|
if (status.code !== Guacamole.Status.Code.SUCCESS) {
|
||||||
|
|
||||||
|
// Disconnect media source node from script processor
|
||||||
|
if (source)
|
||||||
|
source.disconnect();
|
||||||
|
|
||||||
|
// Disconnect associated script processor node
|
||||||
|
if (processor)
|
||||||
|
processor.disconnect();
|
||||||
|
|
||||||
|
// Remove references to now-unneeded components
|
||||||
|
processor = null;
|
||||||
|
source = null;
|
||||||
|
|
||||||
writer.sendEnd();
|
writer.sendEnd();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to retrieve an audio input stream from the browser
|
// Attempt to retrieve an audio input stream from the browser
|
||||||
getUserMedia({ 'audio' : true }, function streamReceived(mediaStream) {
|
getUserMedia({ 'audio' : true }, function streamReceived(mediaStream) {
|
||||||
|
|
||||||
// Create processing node which receives appropriately-sized audio buffers
|
// Create processing node which receives appropriately-sized audio buffers
|
||||||
var processor = context.createScriptProcessor(BUFFER_SIZE, format.channels, format.channels);
|
processor = context.createScriptProcessor(BUFFER_SIZE, format.channels, format.channels);
|
||||||
processor.connect(context.destination);
|
processor.connect(context.destination);
|
||||||
|
|
||||||
// Send blobs when audio buffers are received
|
// Send blobs when audio buffers are received
|
||||||
@@ -252,7 +408,7 @@ Guacamole.RawAudioRecorder = function RawAudioRecorder(stream, mimetype) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Connect processing node to user's audio input source
|
// Connect processing node to user's audio input source
|
||||||
var source = context.createMediaStreamSource(mediaStream);
|
source = context.createMediaStreamSource(mediaStream);
|
||||||
source.connect(processor);
|
source.connect(processor);
|
||||||
|
|
||||||
}, function streamDenied() {
|
}, function streamDenied() {
|
||||||
|
Reference in New Issue
Block a user