49
43

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DiscordAdvent Calendar 2018

Day 21

discord.js でボイスチャットを音声認識してテキストログを取る

Last updated at Posted at 2018-12-21

2020/04/24現在、@TumoiYorozu 様のコメントより、Botがボイスチャンネル接続時に、発話することで動作するとのことです!

2019/11/06現在 この記事通りに音声を受信できません。推測ですが、Discord側の仕様が変わった可能性があります

1 概要

Discord(discord.js) 経由で取得した音声をgoogleの音声解析APIを利用することで、テキスト化する方法を紹介します。
Discord ボイスチャンネルから音声取得する方法は、先日の記事 を参照してください。
ここではDiscord経由で取得した音声データを google API に流し込む方法を中心に紹介します。

*すいません、アドベントカレンダーには音声合成もするって書いていましたが、間に合いませんでした!

本記事は下記の事項を把握していることを前提としています

2 動作確認環境

  • マシン : iMac Late 2015
  • OS : mac OSX 10.14.1 (Mojave)
  • node : v8.12.0(Mac環境でライブラリが動くのがこのバージョンだった)
  • discord.js: 11.4.2

3 やりたいこと

やりたいことは下図のように会話内容をテキストログ化するようなbotです。
ここまでできれば、音声応答botが作れます!!!

スクリーンショット 2018-12-21 19.05.04.png *bot アイコンは キヨイチ様に制作していただきました(https://twitter.com/kiyoichi261)

4 会話音声のテキストログ化

プログラム一式はこちらのレポジトリから取得できますので、合わせてご参照ください。
https://github.com/YoshikazuOota/discord_voice

4.1 google Speech-To-Text へのつなぎ込み

後述する『音声フォーマットの問題』(4.2)がなければ難しいことはなく、 Évelyne Lachance(eslachance) 様のプログラムの createPCMStream の出力を googleのSpeech-To-Text の streamingRecognize に繋いであげるだけです。

主な変更部分(L41-L53)
const audioStream = receiver.createPCMStream(user);
const recognizeStream = speechClient.streamingRecognize(stt_request)
  .on('error', console.error)
  .on('data', (data) => {
    if(data.error === null){
      console.log(`${user.username} : ${data.results[0].alternatives[0].transcript}`);
  }
});
audioStream.pipe(recognizeStream);
v9-voice-reveive_text.js(フルソース)
require('dotenv').config(); // 環境変数の設定(exportしなくてもよくなる)

const Discord = require("discord.js");
const fs = require('fs');

const client = new Discord.Client();
const config = require('./auth.json');

const speech = require('@google-cloud/speech');

const SAMPLE_RATE = 48000;
const speechClient = new speech.v1p1beta1.SpeechClient();
const stt_request ={
    config: {
        encoding: 'LINEAR16', // signed 16bit PCM
        sampleRateHertz: SAMPLE_RATE, // 48kHz
        languageCode: 'ja-jp',
    }
};

client.on('message', msg => {
    if (msg.content.startsWith(config.prefix+'join')) {
        let [command, ...channelName] = msg.content.split(" ");
        if (!msg.guild) {
            return msg.reply('no private service is available in your area at the moment. Please contact a service representative for more details.');
        }
        const voiceChannel = msg.guild.channels.find("name", channelName.join(" "));
        //console.log(voiceChannel.id);
        if (!voiceChannel || voiceChannel.type !== 'voice') {
            return msg.reply(`I couldn't find the channel ${channelName}. Can you spell?`);
        }
        voiceChannel.join()
            .then(conn => {
                msg.reply('ready!');
                // create our voice receiver
                const receiver = conn.createReceiver();

                conn.on('speaking', (user, speaking) => {
                    if (speaking) {
                        msg.channel.sendMessage(`I'm listening to ${user}`);

                        // !! modify Streo to "MONO" !!
                        // this creates a 16-bit signed PCM, "Single" 48KHz PCM stream.
                        const audioStream = receiver.createPCMStream(user);
                        const recognizeStream = speechClient.streamingRecognize(stt_request)
                            .on('error', console.error)
                            .on('data', (data) => {
                                if(data.error === null){
                                    console.log(`${user.username} : ${data.results[0].alternatives[0].transcript}`);
                                }
                            });
                        audioStream.pipe(recognizeStream);

                        // when the stream ends (the user stopped talking) tell the user
                        audioStream.on('end', () => {
                            msg.channel.sendMessage(`I'm no longer listening to ${user}`);
                        });
                    }
                });
            })
            .catch(console.log);
    }
    if(msg.content.startsWith(config.prefix+'leave')) {
        let [command, ...channelName] = msg.content.split(" ");
        let voiceChannel = msg.guild.channels.find("name", channelName.join(" "));
        voiceChannel.leave();
    }
});

client.login(config.token);

client.on('ready', () => {
    console.log('ready!');
});

4.2 discord.js と google Speech-To-Text の音声フォーマット問題

上記のソースのようにできれば幸いなのですが、discord.js と google Speech-To-Text でフォーマットを合わせる必要があります。

オリジナルの discord.js の createPCMStream の出力フォーマットは

  • PCM
  • 16 bit
  • 48Hz
  • 2ch (ステレオ)

ですが google の Speech-To-Text は 1ch しかサポートしていません。(2chで送信する必要がないので当然ですが...)
ここでも前回同様 discord.js のライブラリをいじって対応しました。

補足
何らかの ライブラリなどを使って createPCMStream 出力を 1ch 化すれば良いのですが、良さげなライブラリが見つからず、オーバーヘッドが増えるのも嫌だったので、ライブラリを直接編集しました。

参考 google Speech to text で対応している音声フォーマット
https://cloud.google.com/nodejs/docs/reference/speech/2.1.x/google.cloud.speech.v1p1beta1.html?hl=ja#.AudioEncoding

4.3 discord.js の createPCMStream 出力を モノラル化(1ch)する方法

: この方法は オーディオエンジンに "node-opus"をしている場合のみ有効です。”opusscript”を使用している場合は、この方法を行ってもモノラル化しません。

レポジトリに用意してある
node_modules_modify/discord.js/src/client/voice/opus/NodeOpusEngine.js
を、npm install でローカルに保存されたソース
node_modules/discord.js/src/client/voice/opus/NodeOpusEngine.js
に、マージすることで、createPCMStream の出力が 1ch になります。

diff/* に diffのレポートファイルなどあるので、こちらを参考にマージしてください。

スクリーンショット 2018-12-20 17.18.36.png

4.4 ファイル出力してモノラル化を確認

4.3 まで完了したら、『discord.js で ボイスチャットの音声録音ボットを作ってみる』 の 3.5 の要領で音声保存して、再生してみて正常に音声が再生されることを確認してください。

スクリーンショット 2018-12-20 17.22.16.png

4.5 動作確認

以上を踏まえて、具体的な実行手順を説明します。

(1)レポジトリダウンロード、ライブラリダウンロード

git clone https://github.com/YoshikazuOota/discord_voice
npm install

(2) ライブラリ discord.js の修正

下記の2ファイルを修正してください。

  • node_modules/discord.js/src/client/voice/VoiceUDPClient.js (必要があれば)
  • node_modules/discord.js/src/client/voice/opus/NodeOpusEngine.js

レポジトリで作業している場合は、下記コマンドで修正箇所を確認できます。

 diff node_modules/discord.js node_modules_modify/discord.js 

diff/* にも差分レポートファイルなどを置いておきましたので参照ください。

(3) google cloud の証明書を設置

『Node.jsからGoogle Speech APIを使ってみる』 で紹介されている要領で、

  • .env
  • 証明書のjsonファイル
    を設置してください 

(4) コマンド実行

最後に、コマンド実行するだけです。

node v9-voice-reveive_text.js  

この際、『discord.js で ボイスチャットの音声録音ボットを作ってみる』 にあるように、UDPポートをバイパスするコマンドも実行してください。

udpforwarder --protocol udp4 --port 13698 --address 0.0.0.0 --forwarderPort 0 --forwarderAddress 0.0.0.0 --destinationPort 33333 --destinationAddress 0.0.0.0

上記プログラムを正常実行できると、コンソールに音声に対応するテキストが表示されます。

スクリーンショット 2018-12-20 17.48.57.png

5 最後に

googleの Speech to Text は趣味に使える程度の料金なのに、精度は高く本当にありがたいですね。
あとは辞書登録機能などで専門用語の判定確率が上がれば言うことはないのです。
専門用語の登録機能を考えると、「AmiVoice Cloud」が良さげなのですが、お小遣いでは運用できなさそうなのですよね。

Discord bot で音声を取り扱えれば、アレクサなどとの連携も可能で、アイデア次第でいろいろなことができそうですよね!
よろしければ、皆様のアイデアなどもコメントいただけるととても嬉しいです!

49
43
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
43

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?