質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.87%

  • JavaScript

    8766questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • WebRTC

    23questions

    WebRTC(Web Real-Time Communication)とは、プラグイン無しでウェブブラウザ間の音声通話・ビデオチャットなどリアルタイムコミュニケーションができるオープンフレームワークです。W3CがAPIレベルで、IETFがプロトコルレベルでそれぞれ標準化が進められています。

  • Web Audio

    6questions

    Web Audioは、音声を処理・合成するためのWebアプリケーション向けJavaScript APIです。HTML5から導入されました。オーディオソースの選択、エフェクト・ビジュアライゼーションの追加、パンニングなど特殊効果の適用など多くの機能を持ちます。

PC内で流れる音源の音声データをJSで取得する方法を教えてください!

解決済

回答 1

投稿 2017/01/23 00:37

  • 評価
  • クリップ 2
  • VIEW 621

afroscript.10

score 116

WebRTCやWebAudioなどを使って、PC内で流れる音声を取得しようとしているのですが、方法が分かりません。。。

具体的に言うと、
Youtubeで流れる音を波形データとして取得し、
その音声の波長データを使って3Dの物体生成する、
というのをやっております。

ちなみに近い完成イメージでいうと、今試しているのは、
WebRTCのnavigator.getUserMediaよりPC内臓のマイクの音を拾ってきて、
その波形データを使って3D表現するというのをやっております。
(Youtubeで流したものをわざわざマイクから拾うという、すごく遠回りな方法です。。。)

/*
 * PCのマイクから音をひろってくる
 */

var getUserMedia = navigator.getUserMedia ? 'getUserMedia' :
    navigator.webkitGetUserMedia ? 'webkitGetUserMedia' :
    navigator.mozGetUserMedia ? 'mozGetUserMedia' :
    navigator.msGetUserMedia ? 'msGetUserMedia' :
    undefined;
var astream, micsrc;
var conditions={audio:true, video:false};
const Mic = () => {
    navigator[getUserMedia](
        conditions,
        (stream) => {
            astream=stream;
            micsrc=audioctx.createMediaStreamSource(stream);
            micsrc.connect(audioctx.destination);
            micsrc.connect(analyser);
        },
        (e) => { console.error(e); }
    );
}

//Mic集音開始
Mic();


/*
 * 音声ファイルデータを使用して3Dオブジェクトを生成 
 */

var audioctx = new AudioContext();

var timerId;
var analyser = audioctx.createAnalyser();
analyser.fftSize = 128;
analyser.minDecibels = -100;
analyser.maxDecibels = -30;

var obj;
const DrawGraph = () => {

    var data = new Uint8Array(512);

    scene.remove(obj);

    analyser.getByteFrequencyData(data); //Spectrum Dataで取得

    //Spectrum Dataの値を使って3Dオブジェクト生成
    var geo = new THREE.TorusKnotGeometry( //小数をかけてるのは値を小さくして、3Dオブジェクトのサイズを小さくするため
        Math.round(data[0] * 2.0), //全体的な大きさ
        Math.round(data[1] * 0.3), //チューブの太さ
        Math.round(40), //クネクネの進む方向に対してなん分割するか
        Math.round(8), //チューブ方向に何分割するか
        Math.round(data[2] * 0.3), //なんかクネクネ具合が変わる数値その1
        Math.round(2) //なんかクネクネ具合が変わる数値その2
    );

    var mat = new THREE.MeshBasicMaterial({color: 0xffffff, wireframe:true });
    obj = new THREE.Mesh( geo, mat);
    scene.add(obj);

    requestAnimationFrame(DrawGraph);
}
timerId=requestAnimationFrame(DrawGraph);

これと同様なことを、PC内で流れる音のみの音声データを拾って、実現したいなと思っているのですが、その方法/調べ方が分からず。。。

丸投げちっくな質問の仕方になってしまい大変恐縮なのですが、ぜひ検索ワードのヒントでもいいので、教えていただけると幸いです...!!

よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問に回答があった場合に通知・メールを受け取ることができます。

    クリップした質問はマイページの「クリップ」タブからいつでも見ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+5

私が考えた最強の方法

MediaElement.captureStream()

MediaElementにはcaptureStream()という機能があり、MediaElementから直接MediaStreamを取得することができるメソッドです。
<canvas>にはすでにcaptureStream()がサポートされているのですが、まだ<video>や<audio>はサポートされていません。
ただ、現在はデフォルトで無効になっているという状態で、chrome://flags の"試験運用版のウェブ プラットフォームの機能"(chrome://flags/#enable-experimental-web-platform-features)を有効に設定することで<video>や<audio>でも使用できるようになります。

拡張機能

Youtubeの埋め込みプレイヤーはiframeで埋め込まれるため、コンテンツ(親フレーム)のJSからプレイヤーの<video>にアクセスすることができないため、拡張機能を使用してアクセスします。
拡張機能および、captureStream()の機能を使用することで、Youtube埋め込みプレイヤーから直接Video/Audioストリームを取得することができるようになります。

公開するには

ブラウザーでcaputureStream()がデフォルトで有効になるバージョンになるまで待てればそれで構いませんし、またユーザーに実験中の機能を有効にしてもらうというのが嫌でなければそのまま公開してもいいでしょう。
ユーザーに有効にしてもらうのが嫌というのでしたら、Origin Trialsという仕組みがあります。これは、Originと有効に設定するフラグを登録することで、ユーザーの設定作業無しに、そのOriginにアクセスすると自動的に登録した実験中の機能が有効になるというものです。
詳しくは、Web 標準化のフィードバックサイクルを円滑にする Origin Trials についてを見てください。

サンプルコード

コンテンツページ

index.html

<!DOCTYPE html>
<html>
<head>
    <script src="three.min.js"></script>
    <script src="OrbitControls.js"></script>
    <style>
        html, body {
            width: 100%;
            height: 100%;
            margin: 0;
        }

        iframe {
            width: 360px;
            height: 200px;
            left: 0;
            top: 0;
            position: absolute;
            z-index: 1;
        }
    </style>
</head>
<body>
    <div id="player"></div>
    <script src="app.js"></script>
</body>
</html>


app.js

const extId = 'bbchnfmcdcokmipmngjjniflkoakncbl';
chrome.runtime.sendMessage(extId, 'content tab handle');

const tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
let player;
let inited = false;
const vid = 'bHQqvYy5KYo';

const renderer = new THREE.WebGLRenderer({
    antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(0, 0, 300);
const controls = new THREE.OrbitControls(camera);
const scene = new THREE.Scene();
const obj = new THREE.Mesh();
obj.material = new THREE.MeshBasicMaterial({
    color: 0xffffff,
    wireframe: true
});
obj.geometry = new THREE.TorusKnotGeometry(0, 0, 0, 0, 0, 0);
scene.add(camera);
scene.add(obj);

window.addEventListener('resize', () => {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
})

function onYouTubeIframeAPIReady() {
    player = new YT.Player('player', {
        height: 200,
        width: 200,
        videoId: vid,
        events: {
            onReady: onPlayerReady,
            onStateChange: onPlayerStateChange
        }
    });
}

function onPlayerReady(event) {
    event.target.playVideo();
    DrawGraph();
}

function onPlayerStateChange(event) {
    if (event.data === YT.PlayerState.PLAYING && !inited) {
        chrome.runtime.sendMessage(extId, 'ready');
        inited = true;
    }
}

function stopVideo() {
    player.stopVideo();
}

function DrawGraph() {
    requestAnimationFrame(DrawGraph);
    chrome.runtime.sendMessage(extId, 'get audio data', function(spectrums) {
        obj.geometry.dispose();
        obj.geometry = new THREE.TorusKnotGeometry( //小数をかけてるのは値を小さくして、3Dオブジェクトのサイズを小さくするため
            Math.round(spectrums[0] * 2.0), //全体的な大きさ
            Math.round(spectrums[1] * 0.3), //チューブの太さ
            Math.round(40), //クネクネの進む方向に対してなん分割するか
            Math.round(8), //チューブ方向に何分割するか
            Math.round(spectrums[2] * 0.3), //なんかクネクネ具合が変わる数値その1
            Math.round(2) //なんかクネクネ具合が変わる数値その2
        );
    });
    controls.update();
    renderer.render(scene, camera);
}

拡張機能

manifest.json

{
    "manifest_version": 2,
    "version": "0.0.1",
    "name": "埋め込みプレイヤーオーディオデータ取得テスト",
    "content_scripts": [{
        "matches": [
            "https://www.youtube.com/embed/*"
        ],
        "js": ["iframe_content.js"],
        "run_at": "document_start",
        "all_frames": true
    }],
    "background": {
        "scripts": ["background.js"]
    },
    "externally_connectable": {
        "matches": [
            "https://turbographics2000.github.io/get_youtube_audiodata/"
        ]
    }
}


background.js

let contentTab = null;
chrome.runtime.onMessageExternal.addListener(handleMessage);
chrome.runtime.onMessage.addListener(handleMessage)

function handleMessage(msg, sender, res) {
    console.log(msg);
    switch (msg) {
        case 'content tab handle':
            contentTab = sender.tab.id;
            console.log('contentTab', contentTab);
            break;
        case 'ready':
            chrome.tabs.sendMessage(contentTab, 'ready');
            break;
        case 'get audio data':
            if (!contentTab) return;
            chrome.tabs.sendMessage(contentTab, msg, (data) => {
                res(data);
            });
            break;
    }
    return true;
}


iframe_content.js

const audioctx = new AudioContext();
const analyser = audioctx.createAnalyser();

chrome.runtime.onMessage.addListener((msg, sender, res) => {
    switch (msg) {
        case 'ready':
            let video = document.getElementsByTagName('video')[0];
            let stream = video.captureStream();
            let micsrc = audioctx.createMediaStreamSource(stream);
            analyser.fftSize = 128;
            analyser.minDecibels = -100;
            analyser.maxDecibels = -30;
            micsrc.connect(analyser);
            break;
        case 'get audio data':
            let spectrums = new Uint8Array(analyser.frequencyBinCount);
            analyser.getByteFrequencyData(spectrums);
            res(spectrums);
            break;
    }
});

デモページ

デモページを用意しました。
"試験運用版のウェブ プラットフォームの機能"(chrome://flags/#enable-experimental-web-platform-features)を有効にし(ブラウザーの再起動)、サンプル拡張機能を先にインストールしてから、デモページにアクセスしてください。

謝辞

サンプルコードおよびデモページで、質問のコードを使用させていただきました。

最後に

確実にピンポイントなストリームを取得することができるというのが一番の利点ですが、見ての通り、単純にgetUserMedia()を使用するより、こちらのほうが実装においてははるかに回りくどいものとなっています。
あと、質問のコードではnavigator.getUserMedia()を使用しているようですが、これは古い仕様のものです。
navigator.mediaDevices.getUserMedia()を使用するようにしましょう(Promise)(prefix解決も不要です)。

投稿 2017/01/23 17:57

編集 2017/01/23 18:09

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    以下のような回答は評価を下げられます

    • 間違っている回答
    • 質問の回答になっていない投稿
    • 不快な投稿

    評価を下げる際はその理由をコメントに書き込んでください。

  • 2017/01/23 20:45

    おぉっ!!!!すごい!!!!!
    できるのですね!!!!

    周りの方々から、かなり厳しいとの意見もらってたのであきらめかけてました!!

    本当にありがとうございます!試します!!

    キャンセル

teratailには29人のエキスパートがいます

今すぐはじめる

もっと詳しく

関連した質問

同じタグがついた質問を見る

  • JavaScript

    8766questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • WebRTC

    23questions

    WebRTC(Web Real-Time Communication)とは、プラグイン無しでウェブブラウザ間の音声通話・ビデオチャットなどリアルタイムコミュニケーションができるオープンフレームワークです。W3CがAPIレベルで、IETFがプロトコルレベルでそれぞれ標準化が進められています。

  • Web Audio

    6questions

    Web Audioは、音声を処理・合成するためのWebアプリケーション向けJavaScript APIです。HTML5から導入されました。オーディオソースの選択、エフェクト・ビジュアライゼーションの追加、パンニングなど特殊効果の適用など多くの機能を持ちます。

閲覧数の多いJavaScriptの質問