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.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.