Přejít k navigační liště

Zdroják » JavaScript » HTML5 a přístup k mikrofonu

HTML5 a přístup k mikrofonu

Články JavaScript

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.

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:

Demo 1 screenshot for this tutorial

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:

Image 1 for this tutorial

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) Javascript Node připravil Time Domain data, která jsou třeba pro vizualizaci.

V našem případě bude schéma fungovat následovně:

Image 2 for this tutorial

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">
  &nbsp; &nbsp;
  <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

  1. zdrojový kód na GitHubu
  2. funkční demo z článku

Komentáře

Subscribe
Upozornit na
guest
1 Komentář
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Radek

Díky za parádní minimalistické zpracování pěkného tutoriálu.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.