HTML5 a přístup k mikrofonu

Má váš počítač mikrofon? Pak k němu můžete přistupovat i z webové aplikace. Tedy, pokud jí to dovolíte. Ukážeme vám, jak na to, a můžete si to rovnou vyzkoušet.
Nálepky:
Tento článek je překladem tutoriálu getUserMedia #2 – Microphone Access z webu Web Apprentice a jeho autorem je Rob Jones. Překlad je zde uveden s laskavým svolením autora.
Úvod
Dřívější článek HTML5, getUserMedia a práce s kamerou vám ukázal, jak můžete přistupovat k webkameře a zobrazovat její video pomocí canvasu. Tento článek naváže a představí vám, jak přistupovat k mikrofonu.
Nejjednodušším demem by bylo puštění vstupu z mikrofonu přímo do vašich reproduktorů, jenže pokud nemáte připojená sluchátka, tak by vznikla zpětná vazba. Místo toho použijeme kód z článku o vizualizaci audia a budeme náš hlasový vstup vizualizovat pomocí canvasu pomocí canvasu.
Metoda getUserMedia není zatím implementována ve všech prohlížečích, ovšem to se brzy zlepší…
V prosinci 2013, kdy vznikl tento text, byl getUserMedia podporován jen v prohlížečích Google Chrome a Mozilla Firefox. Aktuální podporu najdete na caniuse.com.
Takhle vypadá zobrazení hlasového vstupu pomocí našeho dema:
Co se děje v kódu
Kód pro vizualizaci založíme na příkladu z článku o vizualizaci audia, kde najdete podrobnější popis, jak vizualizace funguje. V něm fungoval kód dle schématu:
Audio bylo nahráno ze souboru do SourceNode, předáno do DestinationNode (to jsou rozhraní z Web Audio API), který je přehrál na reproduktorech. Současně je převzal Analyser Node (další rozhraní z Web Audio API) a Javascript Node připravil Time Domain data, která jsou třeba pro vizualizaci.
V našem případě bude schéma fungovat následovně:
Audio stream z mikrofonu předáme do Source Node, ovšem nebudeme ho propojovat přímo na Destination Node, ponecháme pouze cestu zajišťující zpracování a zobrazení dat.
V následujícím kódu sdělujeme, že getUserMedias má získat přístup k audio vstupu, ale už ne k video vstupu. Jelikož všechny prohlížeče zatím getUserMedia nepodporují, obalíme kód try / catch blokem a zobrazíme uživateli informaci o případém selhání.
$("#start_button").click(function(e) {
[...]
try {
navigator.getUserMedia(
{ video: false,
audio: true},
setupAudioNodes,
onError);
} catch (e) {
alert('webkitGetUserMedia threw exception :' + e);
}
});
Metoda navigator.getUserMedia zavolá naši funkci setupAudioNodes a předá jí parametrem audio stream. Ten předáme metodě audioContext.createMediaStreamSource, abychom vytvořili sourceNode, který obsahuje přístup ke zvukovému vstupu mikrofonu.
function setupAudioNodes(stream) {
sourceNode = audioContext.createMediaStreamSource(stream);
audioStream = stream;
[...]
}
Následuje komentovaný výpis celého kódu:
<p style="text-align: center">Click Start and begin speaking</p>
<canvas id="canvas" width="800" height="256" ></canvas>
<p id="controls">
<input type="button" id="start_button" value="Start">
<input type="button" id="stop_button" value="Stop">
</p>
<!-- ----------------------------------------------------- -->
<style>
#canvas {
margin-left: auto;
margin-right: auto;
display: block;
background-color: black;
}
#controls {
text-align: center;
}
#start_button, #stop_button {
font-size: 16pt;
}
</style>
<!-- ----------------------------------------------------- -->
<script type="text/javascript">
// Hack pro vypořádání se s vendor prefixy
navigator.getUserMedia = ( navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback, element){
window.setTimeout(callback, 1000 / 60);
};
})();
window.AudioContext = (function(){
return window.webkitAudioContext || window.AudioContext || window.mozAudioContext;
})();
// Globální proměnné pro audio
var audioContext;
var analyserNode;
var javascriptNode;
var sampleSize = 1024; // počet vzorků nutných k nasbírání pro začátek analýzy
// nižší hodnota = rychlejší sonogram
var amplitudeArray; // pole pro frekvenční data
var audioStream;
// Globální proměnné pro kreslení
var column = 0;
var canvasWidth = 800;
var canvasHeight = 256;
var ctx;
$(document).ready(function() {
ctx = $("#canvas").get()[0].getContext("2d");
try {
audioContext = new AudioContext(); // rozhraní pro zpracování audia
} catch(e) {
alert('Web Audio API is not supported in this browser');
}
// Po kliknutí na tlačítko Start dokonči nastavení audio nodů a začni
// zpracovávat zvukový vstup ze vstupního zařízení
$("#start_button").click(function(e) {
e.preventDefault();
clearCanvas();
// získej vstupní zvukový stream a nastav nody
try {
navigator.getUserMedia(
{ video: false,
audio: true},
setupAudioNodes,
onError);
} catch (e) {
alert('webkitGetUserMedia threw exception :' + e);
}
});
// Ukonči zpracovávání audia
$("#stop_button").click(function(e) {
e.preventDefault();
javascriptNode.onaudioprocess = null;
if(audioStream) audioStream.stop();
if(sourceNode) sourceNode.disconnect();
});
});
function setupAudioNodes(stream) {
// vytvoří media stream ze zvukového vstupu (microfonu)
sourceNode = audioContext.createMediaStreamSource(stream);
audioStream = stream;
analyserNode = audioContext.createAnalyser();
javascriptNode = audioContext.createScriptProcessor(sampleSize, 1, 1);
// Vytvoř pole analýzu dat
amplitudeArray = new Uint8Array(analyserNode.frequencyBinCount);
// nastav obsluhu události, která bude vyvolána vždy po nasbírání dostatečného počtu vzorků
// proveď analýzu a nakresli jeden sloupec do vizualizace
javascriptNode.onaudioprocess = function () {
amplitudeArray = new Uint8Array(analyserNode.frequencyBinCount);
analyserNode.getByteTimeDomainData(amplitudeArray);
// nakresli jeden sloupec
requestAnimFrame(drawTimeDomain);
}
// Nyní spojíme nody dohromady
// Nepropojujeme source node do destinace, abychom zabránili zpětné vazbě
sourceNode.connect(analyserNode);
analyserNode.connect(javascriptNode);
javascriptNode.connect(audioContext.destination);
}
function onError(e) {
console.log(e);
}
function drawTimeDomain() {
var minValue = 9999999;
var maxValue = 0;
for (var i = 0; i < amplitudeArray.length; i++) {
var value = amplitudeArray[i] / 256;
if(value > maxValue) {
maxValue = value;
} else if(value < minValue) {
minValue = value;
}
}
var y_lo = canvasHeight - (canvasHeight * minValue) - 1;
var y_hi = canvasHeight - (canvasHeight * maxValue) - 1;
ctx.fillStyle = '#ffffff';
ctx.fillRect(column,y_lo, 1, y_hi - y_lo);
// smyčka přes celý canvas až dokud nenarazíme na jeho konec
column += 1;
if(column >= canvasWidth) {
column = 0;
clearCanvas();
}
}
function clearCanvas() {
column = 0;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// ctx.beginPath();
ctx.strokeStyle = '#f00';
var y = (canvasHeight / 2) + 0.5;
ctx.moveTo(0, y);
ctx.lineTo(canvasWidth-1, y);
ctx.stroke();
}
</script>
Je to sice hodně kódu, ale jen několik řádek se týká vlastního přístupu ke zvukovému vstupu. Snad vám to pomůže při tvorbě vašeho vlastního kódu. Jedná se o úžasnou věc a jakmile bude implementována ve všech prohlížečích, určitě ji začne využívat řada nových aplikací.
Demo a zdrojový kód
- zdrojový kód na GitHubu
- funkční demo z článku
Díky za parádní minimalistické zpracování pěkného tutoriálu.