SpeechRecognizerを使って音声テキスト及び音声データを取得する方法

2013-11-05T00:00:00+00:00 Android Java

昔書いたネタをそのまま書く。

※リソース開放方式まで考慮してないので、そこら辺は活用する場合にはそれなりに要修正必須

※onBufferReceivedが機種依存により呼ばれないのもあるそうです

android.speech.SpeechRecognizerを使って音声からテキストを起こすAPIがあるけど、この際に音声データ自体も保管しておくっていう事をする。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8" ?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="sample.test"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
        <activity android:name=".MainActivity" android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

res/layout/activity_main.xml

<?xml version="1.0" ?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <Button
        android:id="@+id/start_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start" />

    <Button
        android:id="@+id/stop_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="stop" />

</LinearLayout>

ただSpeechRecognizerをスタートするのとストップするだけのボタンを配置

MainActivity.java

package sample.test;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.speech.RecognitionListener;
import android.speech.SpeechRecognizer;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;


public class MainActivity extends Activity {

    private static final int SAMPLING = 8000;
    private SpeechRecognizer recognizer;
    private ByteArrayOutputStream baos = new ByteArrayOutputStream(SAMPLING * 2 * 80);

    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);

        ((Button)findViewById(R.id.start_btn)).setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                if(recognizer != null) {
                    recognizer.startListening(new Intent());
                }
            }
        });

        ((Button)findViewById(R.id.stop_btn)).setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                if(recognizer != null) {
                    recognizer.stopListening();
                    recognizer.destroy();
                }

                OutputStream os = null;

                try {
                    byte[] b = baos.toByteArray();
                    int len = b.length;

                    if(len <= 0) {
                        return;
                    }

                    short type = 1;
                    short channel = 1;
                    short perSampling = 16;

                    os = new FileOutputStream(
                        new File(getExternalCacheDir(),System.currentTimeMillis() + ".wav")
                    );

                    // WAVEヘッダーの書き出し?
                    write(os,"RIFF");
                    writeInt(os, 36 + len);
                    write(os, "WAVE");
                    write(os, "fmt ");
                    writeInt(os, 16);
                    writeShort(os, type);
                    writeShort(os, channel);
                    writeInt(os, SAMPLING);
                    writeInt(os, channel * SAMPLING * perSampling);
                    writeShort(os, (short)(channel * perSampling / 8));
                    writeShort(os, perSampling);
                    write(os, "data");
                    writeInt(os, len);

                    // WAVEボディ(PCM?)の書き出し
                    os.write(b);
                } catch(IOException e) {
                    e.printStackTrace();
                } finally {
                    if(os != null) {
                        try {
                            os.close();
                        } catch(IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
    }

    @Override
    public void onStart() {
        super.onStart();

        recognizer = SpeechRecognizer.createSpeechRecognizer(this);
        recognizer.setRecognitionListener(new RecognitionListener() {

            public void onRmsChanged(float rmsdB) {
            }

            public void onResults(Bundle bundle) {
                List<String> words = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);

                for(String word : words) {
                    Toast.makeText(MainActivity.this, word, Toast.LENGTH_LONG).show();
                }
            }

            public void onReadyForSpeech(Bundle params) {
            }

            public void onPartialResults(Bundle results) {
            }

            public void onEvent(int type, Bundle params) {
            }

            public void onError(int error) {
            }

            public void onEndOfSpeech() {
            }

            public void onBufferReceived(byte[] buffer) {
                try {
                    baos.write(buffer);
                } catch(IOException e) {
                    e.printStackTrace();
                }
            }

            public void onBeginningOfSpeech() {
            }
        });
    }

    @Override
    public void onStop() {
        super.onStop();

        if(recognizer != null) {
            recognizer.stopListening();
            recognizer.destroy();
            recognizer = null;
        }
    }

    private void write(OutputStream out, String value) throws IOException {
        for(int i = 0; i < value.length(); i++) {
            out.write(value.charAt(i));
        }
    }

    private void writeShort(OutputStream out, short value) throws IOException {
        out.write(value >> 0);
        out.write(value >> 8);
    }

    private void writeInt(OutputStream out, int value) throws IOException {
        out.write(value >> 0);
        out.write(value >> 8);
        out.write(value >> 16);
        out.write(value >> 24);
    }
}

っていう感じ。SpeechRecognizerを使ってやる場合とかだと、音声からテキストを起こすだけじゃなくて、その音声を保管しておくことも可能、但し音声はPCMデータになっているので.wavとして保管にするにはWAVEヘッダーもろもろを処理する必要があるっぽい (音声系処理に関してはまったく知らないので)

ちなみにAndroidのソース(AOSP)にはandroid.speech.srec.WaveHeaderっていうのが入ってる(SDK内APIには無い)。それを使えば

WaveHeader wav = new WaveHeader(
    WaveHeader.FORMAT_PCM,
    (short)1,
    11025,
    (short)16,
    b.length
);

OutputStream os = new FileOutputStream(new File("/sdcard/sample.wav"));

wav.write(os);

的に利用する事も可能

※WaveHeaderはAOSP/frameworks/base/core/java/android/speech/srecに入ってる

InputMethodServiceを使ってIMを作るメモ Event Pagesでchrome.contextMenus