mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1156472 - Part 10 - Test AudioCaptureStream. r=pehrsons
This commit is contained in:
parent
0e0e2b315e
commit
6e3d84088b
@ -20,6 +20,114 @@ try {
|
||||
FAKE_ENABLED = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class provides helpers around analysing the audio content in a stream
|
||||
* using WebAudio AnalyserNodes.
|
||||
*
|
||||
* @constructor
|
||||
* @param {object} stream
|
||||
* A MediaStream object whose audio track we shall analyse.
|
||||
*/
|
||||
function AudioStreamAnalyser(ac, stream) {
|
||||
if (stream.getAudioTracks().length === 0) {
|
||||
throw new Error("No audio track in stream");
|
||||
}
|
||||
this.audioContext = ac;
|
||||
this.stream = stream;
|
||||
this.sourceNode = this.audioContext.createMediaStreamSource(this.stream);
|
||||
this.analyser = this.audioContext.createAnalyser();
|
||||
this.sourceNode.connect(this.analyser);
|
||||
this.data = new Uint8Array(this.analyser.frequencyBinCount);
|
||||
}
|
||||
|
||||
AudioStreamAnalyser.prototype = {
|
||||
/**
|
||||
* Get an array of frequency domain data for our stream's audio track.
|
||||
*
|
||||
* @returns {array} A Uint8Array containing the frequency domain data.
|
||||
*/
|
||||
getByteFrequencyData: function() {
|
||||
this.analyser.getByteFrequencyData(this.data);
|
||||
return this.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Append a canvas to the DOM where the frequency data are drawn.
|
||||
* Useful to debug tests.
|
||||
*/
|
||||
enableDebugCanvas: function() {
|
||||
var cvs = document.createElement("canvas");
|
||||
document.getElementById("content").appendChild(cvs);
|
||||
|
||||
// Easy: 1px per bin
|
||||
cvs.width = this.analyser.frequencyBinCount;
|
||||
cvs.height = 256;
|
||||
cvs.style.border = "1px solid red";
|
||||
|
||||
var c = cvs.getContext('2d');
|
||||
|
||||
var self = this;
|
||||
function render() {
|
||||
c.clearRect(0, 0, cvs.width, cvs.height);
|
||||
var array = self.getByteFrequencyData();
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
c.fillRect(i, (256 - (array[i])), 1, 256);
|
||||
}
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
requestAnimationFrame(render);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a Promise, that will be resolved when the function passed as
|
||||
* argument, when called, returns true (meaning the analysis was a
|
||||
* success).
|
||||
*
|
||||
* @param {function} analysisFunction
|
||||
* A fonction that performs an analysis, and returns true if the
|
||||
* analysis was a success (i.e. it found what it was looking for)
|
||||
*/
|
||||
waitForAnalysisSuccess: function(analysisFunction) {
|
||||
var self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
function analysisLoop() {
|
||||
var success = analysisFunction(self.getByteFrequencyData());
|
||||
if (success) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
// else, we need more time
|
||||
requestAnimationFrame(analysisLoop);
|
||||
}
|
||||
analysisLoop();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the FFT bin index for a given frequency.
|
||||
*
|
||||
* @param {double} frequency
|
||||
* The frequency for whicht to return the bin number.
|
||||
* @returns {integer} the index of the bin in the FFT array.
|
||||
*/
|
||||
binIndexForFrequency: function(frequency) {
|
||||
return 1 + Math.round(frequency *
|
||||
this.analyser.fftSize /
|
||||
this.audioContext.sampleRate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reverse operation, get the frequency for a bin index.
|
||||
*
|
||||
* @param {integer} index an index in an FFT array
|
||||
* @returns {double} the frequency for this bin
|
||||
*/
|
||||
frequencyForBinIndex: function(index) {
|
||||
return (index - 1) *
|
||||
this.audioContext.sampleRate /
|
||||
this.analyser.fftSize;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the necessary HTML elements for head and body as used by Mochitests
|
||||
@ -136,7 +244,10 @@ function setupEnvironment() {
|
||||
['media.navigator.permission.disabled', true],
|
||||
['media.navigator.streams.fake', FAKE_ENABLED],
|
||||
['media.getusermedia.screensharing.enabled', true],
|
||||
['media.getusermedia.screensharing.allowed_domains', "mochi.test"]
|
||||
['media.getusermedia.screensharing.allowed_domains', "mochi.test"],
|
||||
['media.getusermedia.audiocapture.enabled', true],
|
||||
['media.useAudioChannelService', true],
|
||||
['media.recorder.audio_node.enabled', true]
|
||||
]
|
||||
}, setTestOptions);
|
||||
|
||||
|
@ -30,6 +30,7 @@ skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g emulator seems to be to
|
||||
[test_dataChannel_noOffer.html]
|
||||
[test_enumerateDevices.html]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[test_getUserMedia_audioCapture.html]
|
||||
[test_getUserMedia_basicAudio.html]
|
||||
skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
|
||||
[test_getUserMedia_basicVideo.html]
|
||||
|
@ -642,39 +642,6 @@ DataChannelWrapper.prototype = {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This class provides helpers around analysing the audio content in a stream
|
||||
* using WebAudio AnalyserNodes.
|
||||
*
|
||||
* @constructor
|
||||
* @param {object} stream
|
||||
* A MediaStream object whose audio track we shall analyse.
|
||||
*/
|
||||
function AudioStreamAnalyser(stream) {
|
||||
if (stream.getAudioTracks().length === 0) {
|
||||
throw new Error("No audio track in stream");
|
||||
}
|
||||
this.stream = stream;
|
||||
this.audioContext = new AudioContext();
|
||||
this.sourceNode = this.audioContext.createMediaStreamSource(this.stream);
|
||||
this.analyser = this.audioContext.createAnalyser();
|
||||
this.sourceNode.connect(this.analyser);
|
||||
this.data = new Uint8Array(this.analyser.frequencyBinCount);
|
||||
}
|
||||
|
||||
AudioStreamAnalyser.prototype = {
|
||||
/**
|
||||
* Get an array of frequency domain data for our stream's audio track.
|
||||
*
|
||||
* @returns {array} A Uint8Array containing the frequency domain data.
|
||||
*/
|
||||
getByteFrequencyData: function() {
|
||||
this.analyser.getByteFrequencyData(this.data);
|
||||
return this.data;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This class acts as a wrapper around a PeerConnection instance.
|
||||
*
|
||||
@ -1559,20 +1526,20 @@ PeerConnectionWrapper.prototype = {
|
||||
* @returns {Promise}
|
||||
* A promise that resolves when we're receiving the tone from |from|.
|
||||
*/
|
||||
checkReceivingToneFrom : function(from) {
|
||||
checkReceivingToneFrom : function(audiocontext, from) {
|
||||
var inputElem = from.localMediaElements[0];
|
||||
|
||||
// As input we use the stream of |from|'s first available audio sender.
|
||||
var inputSenderTracks = from._pc.getSenders().map(sn => sn.track);
|
||||
var inputAudioStream = from._pc.getLocalStreams()
|
||||
.find(s => s.getAudioTracks().some(t => inputSenderTracks.some(t2 => t == t2)));
|
||||
var inputAnalyser = new AudioStreamAnalyser(inputAudioStream);
|
||||
var inputAnalyser = new AudioStreamAnalyser(audiocontext, inputAudioStream);
|
||||
|
||||
// It would have been nice to have a working getReceivers() here, but until
|
||||
// we do, let's use what remote streams we have.
|
||||
var outputAudioStream = this._pc.getRemoteStreams()
|
||||
.find(s => s.getAudioTracks().length > 0);
|
||||
var outputAnalyser = new AudioStreamAnalyser(outputAudioStream);
|
||||
var outputAnalyser = new AudioStreamAnalyser(audiocontext, outputAudioStream);
|
||||
|
||||
var maxWithIndex = (a, b, i) => (b >= a.value) ? { value: b, index: i } : a;
|
||||
var initial = { value: -1, index: -1 };
|
||||
|
110
dom/media/tests/mochitest/test_getUserMedia_audioCapture.html
Normal file
110
dom/media/tests/mochitest/test_getUserMedia_audioCapture.html
Normal file
@ -0,0 +1,110 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test AudioCapture </title>
|
||||
<script type="application/javascript" src="mediaStreamPlayback.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script>
|
||||
|
||||
createHTML({
|
||||
bug: "1156472",
|
||||
title: "Test AudioCapture with regular HTMLMediaElement, AudioContext, and HTMLMediaElement playing a MediaStream",
|
||||
visible: true
|
||||
});
|
||||
|
||||
scriptsReady
|
||||
.then(() => FAKE_ENABLED = false)
|
||||
.then(() => {
|
||||
runTestWhenReady(function() {
|
||||
// Get an opus file containing a sine wave at maximum amplitude, of duration
|
||||
// `lengthSeconds`, and of frequency `frequency`.
|
||||
function getSineWaveFile(frequency, lengthSeconds, callback) {
|
||||
var chunks = [];
|
||||
var off = new OfflineAudioContext(1, lengthSeconds * 48000, 48000);
|
||||
var osc = off.createOscillator();
|
||||
var rec = new MediaRecorder(osc);
|
||||
rec.ondataavailable = function(e) {
|
||||
chunks.push(e.data);
|
||||
};
|
||||
rec.onstop = function(e) {
|
||||
var blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
|
||||
callback(blob);
|
||||
}
|
||||
osc.frequency.value = frequency;
|
||||
osc.start();
|
||||
rec.start();
|
||||
off.startRendering().then(function(buffer) {
|
||||
rec.stop();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get two HTMLMediaElements:
|
||||
* - One playing a sine tone from a blob (of an opus file created on the fly)
|
||||
* - One being the output for an AudioContext's OscillatorNode, connected to
|
||||
* a MediaSourceDestinationNode.
|
||||
*
|
||||
* Also, use the AudioContext playing through its AudioDestinationNode another
|
||||
* tone, using another OscillatorNode.
|
||||
*
|
||||
* Capture the output of the document, feed that back into the AudioContext,
|
||||
* with an AnalyserNode, and check the frequency content to make sure we
|
||||
* have recorded the three sources.
|
||||
*
|
||||
* The three sine tones have frequencies far apart from each other, so that we
|
||||
* can check that the spectrum of the capture stream contains three
|
||||
* components with a high magnitude.
|
||||
*/
|
||||
var wavtone = createMediaElement("audio", "WaveTone");
|
||||
var acTone = createMediaElement("audio", "audioContextTone");
|
||||
var ac = new AudioContext();
|
||||
|
||||
var oscThroughMediaElement = ac.createOscillator();
|
||||
oscThroughMediaElement.frequency.value = 1000;
|
||||
var oscThroughAudioDestinationNode = ac.createOscillator();
|
||||
oscThroughAudioDestinationNode.frequency.value = 5000;
|
||||
var msDest = ac.createMediaStreamDestination();
|
||||
|
||||
oscThroughMediaElement.connect(msDest);
|
||||
oscThroughAudioDestinationNode.connect(ac.destination);
|
||||
|
||||
acTone.mozSrcObject = msDest.stream;
|
||||
|
||||
getSineWaveFile(10000, 10, function(blob) {
|
||||
wavtone.src = URL.createObjectURL(blob);
|
||||
oscThroughMediaElement.start();
|
||||
oscThroughAudioDestinationNode.start();
|
||||
wavtone.loop = true;
|
||||
wavtone.play();
|
||||
acTone.play();
|
||||
});
|
||||
|
||||
var constraints = {audio: {mediaSource: "audioCapture"}};
|
||||
|
||||
return getUserMedia(constraints).then((stream) => {
|
||||
checkMediaStreamTracks(constraints, stream);
|
||||
window.grip = stream;
|
||||
var analyser = new AudioStreamAnalyser(ac, stream);
|
||||
analyser.enableDebugCanvas();
|
||||
return analyser.waitForAnalysisSuccess(function(array) {
|
||||
// We want to find three frequency components here, around 1000, 5000
|
||||
// and 10000Hz. Frequency are logarithmic. Also make sure we have low
|
||||
// energy in between, not just a flat white noise.
|
||||
return (array[analyser.binIndexForFrequency(50)] < 50 &&
|
||||
array[analyser.binIndexForFrequency(1000)] > 200 &&
|
||||
array[analyser.binIndexForFrequency(2500)] < 50 &&
|
||||
array[analyser.binIndexForFrequency(5000)] > 200 &&
|
||||
array[analyser.binIndexForFrequency(7500)] < 50 &&
|
||||
array[analyser.binIndexForFrequency(10000)] > 200);
|
||||
}).then(finish);
|
||||
}).catch(finish);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -136,7 +136,7 @@
|
||||
]);
|
||||
test.chain.append([
|
||||
function PC_LOCAL_CHECK_WEBAUDIO_FLOW_PRESENT(test) {
|
||||
return test.pcRemote.checkReceivingToneFrom(test.pcLocal);
|
||||
return test.pcRemote.checkReceivingToneFrom(test.audioCtx, test.pcLocal);
|
||||
}
|
||||
]);
|
||||
test.chain.append([
|
||||
|
@ -32,7 +32,7 @@ runNetworkTest(function() {
|
||||
]);
|
||||
test.chain.append([
|
||||
function CHECK_AUDIO_FLOW(test) {
|
||||
return test.pcRemote.checkReceivingToneFrom(test.pcLocal);
|
||||
return test.pcRemote.checkReceivingToneFrom(test.audioContext, test.pcLocal);
|
||||
}
|
||||
]);
|
||||
test.run();
|
||||
|
Loading…
Reference in New Issue
Block a user