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

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

ただいまの
回答率

88.11%

WebSocketを利用してGoogle Cloud Speech APIのストリーミング認識を実行したい

受付中

回答 0

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,907

score 66

 前提・実現したいこと

[Python3] Google Cloud Speech gRPC APIでストリーミング音声認識の実行
こちらではPyAudioを利用してマイク入力を利用されていますが、これをブラウザからのマイク入力に置き換えるにはどうすべきか、ということです。
音声のバイトの扱いがonaudioprocessで渡されたものと、PyAudioのStreamで得られたものでどう異なるのかを知りたいです。

 該当のソースコード

import audioop
import math
import os
import sys
import threading
import time

import tornado.websocket

from google.cloud import speech
from google.cloud.speech import enums, types
from google.rpc import code_pb2

LANGUAGE_CODE = 'ja_JP'
FRAME_SECONDS = 0.1
SILENT_DECIBEL = 40
SAMPLE_SIZE = 2
SAMPLE_RATE = 44100

frames = []
silent_frames = []
should_finish_stream = False
is_recording = False


class stdout:
    BOLD = "\033[1m"
    END = "\033[0m"
    CLEAR = "\033[2K"


def bold(string):
    return stdout.BOLD + string + stdout.END


def printr(string):
    sys.stdout.write("\r" + stdout.CLEAR)
    sys.stdout.write(string)
    sys.stdout.flush()


class Result:
    def __init__(self):
        self.transcription = ""
        self.confidence = ""
        self.is_final = False


recognition_result = Result()


class StreamVoiceSocketHandler(tornado.websocket.WebSocketHandler):
    """ 音声受付WebSocketハンドラ """

    def open(self):
        print("connected")

        self.thread = TestThread()
        self.thread.start()

    def on_message(self, message):
        frames.append(message)

    def on_close(self):
        print("disconnected")


class TestThread(threading.Thread):
    def run(self):
        global is_recording
        global should_finish_stream

        self.client = speech.SpeechClient()
        self.config = types.RecognitionConfig(
            encoding=enums.RecognitionConfig.AudioEncoding.LINEAR16,
            sample_rate_hertz=SAMPLE_RATE,
            language_code=LANGUAGE_CODE)
        self.streaming_config = types.StreamingRecognitionConfig(config=self.config)

        while True:
            should_finish_stream = False
            is_recording = False
            self.run_recognition_loop()

    def run_recognition_loop(self):
        global frames
        global silent_frames
        global is_recording
        global should_finish_stream

        if len(silent_frames) > 4:
            silent_frames = silent_frames[-4:]

        while not is_recording:
            time.sleep(FRAME_SECONDS // 4)

            if len(frames) > 4:
                for frame_index in range(4):
                    data = frames[frame_index]
                    rms = audioop.rms(data, 2)
                    decibel = 20 * math.log10(rms) if rms > 0 else 0
                    if decibel < SILENT_DECIBEL:
                        silent_frames += frames[0:frame_index + 1]
                        del frames[0:frame_index + 1]
                        return

                is_recording = True
                frames = silent_frames + frames
                silent_frames = []

        try:
            self.listen_loop(self.client.streaming_recognize(self.streaming_config, self.request_stream()))
            printr(" ".join((bold(recognition_result.transcription), "    ", "confidence: ",
                             str(int(recognition_result.confidence * 100)), "%")))
            print()
        except Exception as e:
            print(str(e))

    def listen_loop(self, recognize_stream):
        global should_finish_stream
        global recognition_result

        for resp in recognize_stream:
            if resp.error.code != code_pb2.OK:
                raise RuntimeError(resp.error.message)

            for result in resp.results:
                for alt in result.alternatives:
                    recognition_result.transcription = alt.transcript
                    recognition_result.confidence = alt.confidence
                    recognition_result.stability = result.stability
                    printr(" ".join(
                        (alt.transcript, "    ", "stability: ", str(int(result.stability * 100)), "%")))

                if result.is_final:
                    recognition_result.is_final = True
                    should_finish_stream = True
                    return

    def request_stream(self):
        while True:
            time.sleep(FRAME_SECONDS / 4)

            if should_finish_stream:
                return

            if len(frames) > 0:
                yield types.StreamingRecognizeRequest(audio_content=frames.pop(0))
function start_recording() {
    let server = null;
    let AudioContext = null;

    function connect() {
        return new Promise(function (resolve, reject) {
            server = new WebSocket("wss://" + location.host + "/ws/stream_voice");
            server.onopen = function () {
                resolve(server);
                console.log('connected');
            };
            server.onerror = function (err) {
                reject(err);
            };
        });
    }

    connect().then(function (server) {
        const handleSuccess = function (stream) {
            AudioContext = window.AudioContext || window.webkitAudioContext;
            const context = new AudioContext();
            const input = context.createMediaStreamSource(stream);
            const processor = context.createScriptProcessor(1024, 1, 1);
            input.connect(processor);
            processor.connect(context.destination);

            processor.onaudioprocess = function (e) {
                const voice = e.inputBuffer.getChannelData(0);
                server.send(voice.buffer);
            };
        };

        navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(handleSuccess);
    }).catch(function (err) {
    });
}

 補足情報(FW/ツールのバージョンなど)

python 3.6.4
tornado 4.5.3
google-cloud-speech 0.32.0

 追記

デシベルの判定の部分ですでにおかしいですね。
やはりonaudioprocessで渡すバッファをそのままではダメなようです。

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

    クリップした質問は、後からいつでもマイページで確認できます。

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

まだ回答がついていません

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

  • ただいまの回答率 88.11%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

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