Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

@a-baba

ブラウザでgetUserMediaしたストリームからflacファイルを作成してみる

これは2017年WebRTCアドベントカレンダーの23日目の記事です。

はじめに

最近、話題のスマートスピーカーGoogle HomeのCMようなことブラウザでもやりたい。

ということで

いろいろ調べてみると最もお手軽なのはWeb_Speech_APIしたが、ブラウザ互換性を見ると
Safari未サポートということなのでcloud speech API用の音声ファイルを作ってみます。

ちなみにGoogle Cloud Speech APILINEAR16 または MULAW でエンコードされたwavファイルも対応
しているのでブラウザでマイク音声からwavファイルを生成する手引 1
こちらで十分なのですが

今回は違う形式の音声ファイル(flac)をブラウザで作成したいので
ライブラリないかなと探していた所、いいのありました!

ライブラリ

Flacエンコード用
https://github.com/mmig/libflac.js
MIT License

Blob保存
https://github.com/eligrey/FileSaver.js
MIT License

WebWorkerを使ったDemoも用意されてます
https://mmig.github.io/speech-to-flac/

実装方法

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>flac recorder</title>
</head>
<body>
    <input type="button" id="start-button" onclick="startRecording();" value="Rec"></input>
    <input type="button" id="stop-button" onclick="stopRecording();" value="Stop"></input>
    <script type="text/javascript" src="libflac3-1.3.2.min.js" /></script>
    <script type="text/javascript" src="FileSaver.min.js" /></script>
    <script type="text/javascript">
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia;
    window.URL = window.URL || window.webkitURL;

    var encoder = null;
    var recording = false;
    var localStream = null;
    var audio_context = null;
    var source = null;
    var node = null;

    function startRecording() {
        //worker開始
        encoder = new Worker('encoder.js');
        encoder.onmessage = function(evt) {
            if (evt.data.cmd == 'end') {
                //flac保存処理
                saveAs(evt.data.buf, 'output.flac');
                //worker破棄
                encoder.terminate();
                encoder = null;
            }
        };

        navigator.getUserMedia({ video: false, audio: true },
            function(stream) { // for success case
                localStream = stream;
                gotStream(localStream);
            },
            function(err) { // for error case
                console.log(err);
            }
        );
    }


    function gotStream(stream) {
        console.log('start recording');
        recording = true;

        var audio_context;
        if (typeof webkitAudioContext !== 'undefined') {
            audio_context = new webkitAudioContext;
        } else if (typeof AudioContext !== 'undefined') {
            audio_context = new AudioContext;
        }
        source = audio_context.createMediaStreamSource(stream);

        if (source.context.createJavaScriptNode) {
            node = source.context.createJavaScriptNode(4096, 1, 1);

        } else if (source.context.createScriptProcessor) {
            node = source.context.createScriptProcessor(4096, 1, 1);
        }

        var samplerate = audio_context.sampleRate; //44100 または 48000
        //flac初期処理
        encoder.postMessage({ cmd: 'init', config: { samplerate: samplerate, bps: 16, channels: 1, compression: 5 } });

        node.onaudioprocess = function(evt) {
            if (!recording) {
                return;
            }

            var channel = evt.inputBuffer.getChannelData(0);
           //flacエンコード
            encoder.postMessage({ cmd: 'encode', buf: channel });
        };

        source.connect(node);
        node.connect(audio_context.destination);
    }

    function stopRecording() {
        if (!recording) {
            return;
        }
        console.log('stop recording');
        var tracks = localStream.getAudioTracks()
        for (var i = tracks.length - 1; i >= 0; --i) {
            tracks[i].stop();
        }
        recording = false;
        //worker終了
        encoder.postMessage({ cmd: 'finish' });

        source.disconnect();
        node.disconnect();
        source = node = null;
    };
    </script>
</body>

</html>

おわりに

そのうちMediaRecorderのmimeTypeにflacが追加されるのを期待しますw!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What are the problem?