★はじめに★
第7話 音声認識機能を使ったVアプリを作る
[JAVA PRESS Vol.34]

ゲーム:こたえて!そらみ
対応端末:V601SH・J-SH53


 テキスト 布留川 英一 (ん・ぱか工房)
 イラスト つきみの せひら (せひらねっと)



へにへに〜、大変大変っ!ボーダフォンの契約純増数は今月も超閑古鳥なのっ!これじゃあauに差を付けられる一方だよぅ!
前々回さんざん酷いこと言ってたくせに、なんで今さらボーダフォンの心配してるのさ、そらみちゃん…。まあ、ツーカーみたいに契約数がマイナスになってないだけマシじゃない?
今まで企業しか作れなかった50Kや256Kアプリの開発環境もやっと一般公開されたし、テレビ見れたり犬とおしゃべりできたりする端末だって出たし、
近ごろようやく面白くなってきたんだけどなぁ…。
社名が海外産のマイナーくさいのに変わっちゃっただけに、社名ールで築き上げたブランド力もすっかり落ちぶれちゃったんだよ…。
座布団没収。そんなことより、ボーダフォンのVアプリ作ろうよぉ〜。今のボーダフォンって、ハードメーカー末期のSE○Aみたいな必死さで胸がキュンキュンうずいちゃうのぉ〜!
はいはい。そらみちゃんがいかに腐女子かってことはよく分かりました。それじゃあ、ボーダフォンならではの音声認識機能を使って、そらみちゃんと会話するゲームを作ってみるのはどうかな?
いわゆる『シー○ン』とか『ピ○チュウげんきでちゅう』みたいなのかな?そんなことまでできるなんて、やっぱりボーダフォンって無駄にスゴイよね。目の付け所がシャープ(のみ)だよねっ!
………何はともあれ、作ってみよっか(汗)


★Vアプリ★


Vアプリ
NTTドコモの携帯電話のWeb閲覧サービスを「iモード」、アプリのダウンロードサービスを「iアプリ」と呼ぶのに対し、ボーダフォンの携帯電話のWeb閲覧サービスを「ボーダフォンライブ!」、アプリのダウンロードサービスを「Vアプリ」と呼びます。「Vアプリ」は以前「Javaアプリ」と呼ばれていましたが、J-PHONEからボーダフォンへの社名変更に伴い変更されました。
WEB閲覧サービス アプリのダウンロードサービス
NTTドコモ iモード iアプリ
ボーダフォン ボーダフォンライブ! Vアプリ
au EZWeb EZアプリ


MIDPとJSCL
「MIDP(Mobile Information Device Profile)1.0」は、ボーダフォンやauの携帯電話をはじめとする多くの携帯端末で採用されているJavaのAPI仕様です。携帯電話だけでなくPDAまでも視野に入れて設計されています。

しかし、携帯端末の進化ははやく、MIDP1.0の仕様だけではアプリ作成には不十分なため、Vアプリには「JSCL(J-PHONE Specific Class Libraries)」と呼ぶ拡張APIが用意されました。「JSCL」には「JSCL1.0」「JSCL1.1」「JSCL1.2」の3つのバージョンがあり、旧Jフォン端末では,「JSCL1.0」を採用している端末を「C4型」、「JSCL1.1」を採用している端末を「P4型」、「JSCL1.2」を採用している端末を「P5型」と呼びます。ボーダフォン端末ではこれらの型名を使わなくなりましたが、「V601N」と「V801SA」はP4型、「V601SH」はP5型相当の機能を持っているので、表に含めています。
主な拡張機能は次の通りです。
各JSCLにおいて拡張された機能
拡張API 対応機種 前JSCLからの拡張点
C4型 JSCL1.0 J-SH07/J-T06/J-D05/J-D06/
J-N04/J-N05/J-SH08/J-SH09/
J-T08/J-SH010/J-D08/J-T010/
V401SH
-
P4型 JSCL1.1 J-SH51/J-K51/J-T51/J-P51/
J-SA51/J-SH52/J-N51
V601N/V801SA
圧縮データの復元
P5型 JSCL1.2 J-SH53
V601SH
音声認識
ブラウザやメーラとの連携
音声通話連携
赤外線通信・赤外線リモコン
カメラ・コードリーダー制御など



★開発環境を整える★


今回のゲームを作るのに必要な開発ツールは次の5つです。全て無償で入手できます。


Java 2 SDK, Standard Edition Version(JDK1.3)
パソコン上で動くJavaアプリを作るための開発キットです。サン・マイクロシステムズのサイトで入手できます。次に説明する「J2ME Wireless Toolkit」を実行するのに必要なので、それより先にインストールします。インストーラの指示に従ってインストールしてください。JDK1.4でない点に注意してください。

J2ME Wireless Toolkit 1.0.3(日本語版)
MIDP1.0仕様のJavaアプリを作るための開発キットです。サン・マイクロシステムズのサイトで入手できます。インストーラの指示に従ってインストールします。J2ME Wireless Toolkit 2.0でない点に注意してください。MIDPのAPIリファレンスはdocsディレクトリの中(C:\J2mewtk\docs\api)にあります。

J-SKY Application Emulator(P4型) Ver.2.0
Vアプリを作るための開発キットです。ボーダフォンのサイトで入手できます。インストーラの指示に従ってインストールしてください。同サイトには、 などの開発に役立つドキュメントもあるので、いっしょに入手してください。

P5型開発用Stubclass
J-SKY Application Emulator(P4型)をP5型に対応させるための差分ファイルです。ボーダフォンのサイトで入手できます。ダウンロードと解凍を行うとP5_stubclassesディレクトリが出現するので、そのディレクトリ内にある全てのディレクトリ(comとjavaとjavax)をZIP圧縮して「stbclasses.zip」を生成します。それをJ-SKY Application Emulator(P4型)に含まれている同名ファイル「C:\J-PHONE-SDK\lib\stbclasses.zip(*1)」に上書きすればインストール完了です。

(*1)パスはJ-SKY Application Emulator(P4型)のインストール先によって変わります。

Voice Recognition Programming for Vodafone
Vアプリで音声認識を実装する時に役立つドキュメントです。「Mascot Capsule Toolkit for Vodafone」のサイトで入手できます。


執筆時現在(2003年12月)、P5型用の開発キットはリリースされていませんが、この記事を読む頃にはリリースしているかもしれません。その時は、「J-SKY Application Emulator(P4型)」と「P5型開発用Stubclass」の代りにP5型用の開発キットを使ってください。



★Vアプリ開発の基礎★


まずはじめに、「Hello World!」という文字列を表示するプログラムを作りながら、Vアプリ開発の基礎について説明します。今回のプログラムは、次の2つのクラスで構成されています。

HelloWorldクラス
HelloWorldクラスは、プログラムの本体となるクラスです。プログラムファイルは前々回(JAVA PRESS Vol.32)と同じものなので説明は省略します。
HelloWorld.java
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

//HelloWorld(本体)
public class HelloWorld extends MIDlet {

    //コンストラクタ
    public HelloWorld() {
        Display.getDisplay(this).setCurrent(new HelloCanvas());
    }

    //アプリの開始
    public void startApp() {
    }

    //アプリの一時停止
    public void pauseApp() {
    }

    //アプリの終了
    public void destroyApp(boolean unconditional) {
    }
}      


HelloCanvasクラス
HelloCanvasクラスは、キャンバスとなるクラスです。こちらの説明も省略します。
HelloCanvas.java
import javax.microedition.lcdui.*;

//HelloWorld(キャンバス)
public class HelloCanvas extends Canvas {

    //描画
    public void paint(Graphics g) {
        g.setColor(255,255,255);
        g.fillRect(0,0,getWidth(),getHeight());
        g.setColor(0,0,0);
        g.drawString("Hello World!",0,0,g.LEFT|g.TOP);
    }
}


コンパイルと検証
ソースコードの準備ができたら「javac」でコンパイルします。
javac -bootclasspath C:\J-PHONE-SDK\lib\stubclasses.zip -d . -g:none *.java

「-bootclasspath」にはJ-SKY Application Emulatorのlibディレクトリ内にある「stubclasses.zip」を指定します。「-g:none」はデバッグ情報を排除するオプションで、これによってクラスファイルのサイズを小さくしています。

次に「preverify」でクラスファイルを検証します。J2SEでは実行時にクラスが安全かどうかチェックしますが、処理能力が低い携帯端末ではかなりの負担になってしまいます。そこでJ2ME CLDCでは実行前に「preverify」によって検証を行い、実行時の負荷を減らしています。preverifyのコマンドは次の通りです。
C:\java\J2mewtk\bin\preverify -d . -classpath .;C:\J-PHONE-SDK\lib\stubclasses.zip HelloWorld
C:\java\J2mewtk\bin\preverify -d . -classpath .;C:\J-PHONE-SDK\lib\stubclasses.zip HelloCanvas

「preverify.exe」はJ2ME Wireless Toolkitのbinディレクトリにあります。「-classpath」にはカレントディレクトリと「stubclasses.zip」を指定します。「-d」で出力先をカレントディレクトリに設定しているので、検証前のクラスファイルに上書きされる形で、検証済みクラスファイルが出力されます。


JARファイルの作成
JARファイルはVアプリの実行ファイルで、 をまとめたものです。

マニフェストファイルは、JARファイルの属性情報(アプリ名やバージョン等)を記述したテキストファイルで、必須属性は次の6つです。
マニフェストファイルの必須属性
MIDlet-Name:名前(UTF-8、16バイト以下)
MIDlet-Vendor:ベンダ名(UTF-8、16バイト以下)
MIDlet-Version:バージョン(16バイト以下)
MIDlet-1:アプリ名,アイコンファイル名,クラス名(UTF-8、70バイト以下)
MicroEdition-Profile:プロファイル名
MicroEdition-Configuration:コンフィギュレーション名

今回のプログラムのマニフェストファイルは次のようになります。
MANIFEST.MF
MIDlet-Name: HelloWorld
MIDlet-Vendor: NPAKA
MIDlet-Version: 1.0
MIDlet-1: HelloWorld, ,HelloWorld
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-1.0

検証済みクラスファイルとマニフェストファイルをまとめてJARファイルを生成するコマンドの書式は次の通りです。
jar cmf <マニフェストファイル名> <JARファイル名> <JARに含めるファイル名1> <JARに含めるファイル名2> …

今回は、マニフェストファイル名「MANIFEST.MF」、JARファイル名「HelloWorld」、JARに含めるファイル名「HelloWorld.class」「HelloCanvas.class」を指定するので、次のようになります。
jar cmf MANIFEST.MF HelloWorld.jar HelloWorld.class HelloCanvas.class


JADファイルの作成
JADファイルはマニフェストファイルと同様に、JARファイルの属性情報を記述したテキストファイルで、JARファイルとは別ファイルとして存在します。携帯端末はJARファイルをダウンロードする時、先にJADファイルを読み込み、実行可能かどうかチェックしてから、JARファイルにアクセスしています。


必須属性は次の5つです。
JADファイルの必須属性
MIDlet-Name:名前(UTF-8、16バイト以下)
MIDlet-Vendor:ベンダ名(UTF-8、16バイト以下)
MIDlet-Version:バージョン(16バイト以下)
MIDlet-Jar-Size: JARファイルのサイズ
MIDlet-Jar-URL: JARファイル名(5バイト以上24バイト以下)

今回のプログラムのJADファイルは次のようになります。
HelloWorld.jad
MIDlet-Name: HelloWorld
MIDlet-Vendor: NPAKA
MIDlet-Version: 1.0
MIDlet-Jar-Size: 1290
MIDlet-Jar-URL: HelloWorld.jar


MIDlet-Jar-Sizeの更新
MIDlet-Jar-Sizeの値はプログラムを変更すると変わり、そのたびにJADファイルを書き換えるのはとても面倒です。そこで、JARファイルのサイズをJADファイルのMIDlet-Jar-Sizeに書き込むJ2SEのプログラムを作りました。
SizeUpdate.java
import java.io.*;
import java.util.*;

//MIDlet-Jar-Sizeを更新するプログラム
public final class SizeUpdate {

    //更新
    public void update(String name) {
        File file;
        List list;
        String str;

        //名前補正
        if (name.endsWith(".jad")) {
            name=name.substring(0,name.length()-4);
        }

        //JADファイルチェック
        file=new File(name+".jad");
        if(!file.exists()) {
            System.out.println(file.toString()+"が見つかりません。");
            return;
        }

        //JARファイルチェック
        file=new File(name+".jar");
        if(!file.exists()) {
            System.out.println(file.toString()+"が見つかりません。");
            return;
        }

        try {
            //更新
            list=readFile(name+".jad");
            for(int i=0;i<list.size();i++) {
                str=(String)list.get(i);
                if(str.startsWith("MIDlet-Jar-Size:")) {
                    list.set(i,"MIDlet-Jar-Size: "+file.length());
                }
            }
            writeFile(name+".jad",list);
            System.out.println(name+".jadのサイズを更新しました。["+
                file.length()+"byte]");
        } catch(IOException e) {
            e.printStackTrace();
        }
    }

    //ファイルの読み込み
    private List readFile(String name) throws IOException {
        ArrayList       list=new ArrayList();
        FileInputStream in  =null;
        BufferedReader  br  =null;
        try {
            in=new FileInputStream(name);
            br=new BufferedReader(new InputStreamReader(in));
            for(String str=br.readLine();str!= null;str=br.readLine()) {
                list.add(str);
            }
            br.close();
            in.close();
        } catch (Exception e) {
            try {
                if(br!=null) br.close();
                if(in!=null) in.close();
            } catch(IOException e2) {
            }
        }
        return list;
    }

    //ファイルの書き込み
    private void writeFile(String name,List list) throws IOException {
        FileOutputStream out=null;
        PrintWriter      pw =null;
        try {
            out=new FileOutputStream(name);
            pw =new PrintWriter(out);
            for(int i=0;i<list.size();i++) {
                pw.print((String)list.get(i)+"\n");
            }
            pw.flush();
            pw.close();
            out.close();
        } catch (Exception e) {
            if(pw != null) pw.close();
            if(out!= null) out.close();
        }
    }

    //メイン
    public static void main(String args[]) {
        if(args.length<1) {
            System.out.println("java SizeUpdate JADファイル名");
            System.exit(-1);
        }
        SizeUpdate sizeupdate=new SizeUpdate();
        sizeupdate.update(args[0]);
    }
}

コマンドの書式は、次の通りです。
java -classpath <SizeUpdate.classを置いたディレクトリ> SizeUpdate <JADファイル名>

C:\workディレクトリにSizeUpdate.classを置き、HelloWorld.jadのMIDlet-Jar-Sizeの値を更新するコマンドは、次の通りです。
java -classpath C:\work SizeUpdate HelloWorld.jad


ファイルサイズの制限
VアプリにもiアプリやEZアプリと同様に、ファイルサイズ制限があります。C4型は「JARファイル+JADファイル+データ保存領域」の合計が50Kバイト、P4型は100Kバイト、P5型は256Kバイトまで使えます。
ファイル種別 ファイルサイズ 合計サイズ
C4型 JADファイル
JARファイル
データ保存領域
3Kバイト
50Kバイト
50Kバイト
50Kバイト
P4型 JADファイル
JARファイル
データ保存領域
3Kバイト
80Kバイト
50Kバイト
100Kバイト
P5型 JADファイル
JARファイル
データ保存領域
3Kバイト
200Kバイト
200Kバイト
256Kバイト



★音声認識を使ってみる★


次に、音声認識を使ったプログラム「VoiceRecogEx」を作ります。画面に"選択キーで開始!!"と表示されている時に選択キーを押すと、"音声認識を開始"と表示されるので電話に向かって話し掛けてください。 のうち、どの言葉に一番近いかを「認識スコア」として表示されます。認識スコアが10以下の時はその言葉を発音した可能性が低く、1位の認識スコアが200以上で2位との差が100以上の時は発音した可能性が高いです。


今回のプログラムは、以下の2つのクラスで構成されています。

VoiceRecogExクラス
VoiceRecogExクラスは、プログラムの本体となるクラスです。
VoiceRecogEx.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

//音声認識を使ってみる(本体)
public class VoiceRecogEx extends MIDlet {

    //コンストラクタ
    public VoiceRecogEx() {
        Display.getDisplay(this).setCurrent(new VoiceRecogCanvas());
    }

    //アプリの開始
    public void startApp() {
    }

    //アプリの一時停止
    public void pauseApp() {
    }

    //アプリの終了
    public void destroyApp(boolean unconditional) {
    }
}


VoiceRecogCanvasクラス
VoiceRecogCanvasクラスは、キャンバスとなるクラスです。画面にはString型配列infoに含まれる文字列を表示します。
VoiceRecogCanvas.java
import com.j_phone.io.*;
import javax.microedition.lcdui.*;

//音声認識を使ってみる(キャンバス)
class VoiceRecogCanvas extends Canvas
    implements VoiceRecognitionListener {
    private final static String DICT_WORD[]={  //単語定数
        null,"オハヨウ","コンニチワ","コンバンワ"};
    private VoiceRecognitionDictionary dict;   //辞書
    private VoiceRecognition recog;            //音声認識
    private String[] info={"選択キーで開始!!"};//情報

    //キープレスイベント
    public void keyPressed(int keyCode) {
        int action=getGameAction(keyCode);

        //音声認識を開始
        if (action==FIRE) {
            try {
                //情報の表示
                info=new String[]{"音声認識を開始"};
                repaint();

                //辞書の生成
                dict=new VoiceRecognitionDictionary();
                for (int i=1;i<DICT_WORD.length;i++) {
                    dict.setWord(i,DICT_WORD[i]);
                }

                //音声認識を開始
                recog=VoiceRecognition.getInstance();
                recog.setVoiceRecognitionListener(this);
                recog.recognize(dict,4000,4000,3);
            } catch (Exception e) {
                //エラーの表示
                info=new String[]{"エラー",
                    e.getClass().getName()};
                repaint();
            }
        }
    }

    //音声認識開始を通知
    public void recognitionStarted() {
        //情報の表示
        info=new String[]{"音声認識中…"};
        repaint();
    }

    //音声認識完了を通知
    public void recognized(int num) {
        //情報の表示
        info=new String[num];
        for (int i=1;i<=num;i++) {
            info[i-1]=DICT_WORD[recog.getCandidateWord(i)]+
                "["+recog.getCandidateScore(i)+"]";
        }
        repaint();
    }

    //音声認識失敗を通知
    public void recognitionFailed(int reason) {
        //情報の表示
        info=new String[]{"音声認識失敗"};
        repaint();
    }

    //描画
    public void paint(Graphics g) {
        g.setColor(255,255,255);
        g.fillRect(0,0,getWidth(),getHeight());
        g.setColor(0,0,0);
        for (int i=0;i<info.length;i++) {
            g.drawString(info[i],0,i*15,g.LEFT|g.TOP);
        }
    }
}


com.j_phone.ioパッケージ
音声認識を行うためのクラスは、com.j_phone.ioパッケージに含まれているので、importしてください。
VoiceRecogCanvas.javaの一部
import com.j_phone.io.*;


キーイベント
キーイベントは、方向キーや数字キーを押したり離したりする時に発生するイベントです。キーを押すと実機からkeyPressed()メソッドが呼ばれ、キーを離すとkeyReleased()メソッドが呼ばれます。
void keyPressed(int keyCode)
keyCode:キーコード
void keyReleased(int keyCode)
keyCode:キーコード

方向キーと決定キーは、キーコードをCanvasクラスのgetGameAction()メソッドに渡して得られる値「Game Action」によって、どのキーが押されたかを判断します。Canvasクラスで持っているGame Action定数は次の9つです。
Game Action定数
Canvas.UP 上キー
Canvas.DOWN 下キー
Canvas.LEFT 左キー
Canvas.RIGHT 右キー
Canvas.FIRE 決定キー
Canvas.GAME_A ゲームA
Canvas.GAME_B ゲームB
Canvas.GAME_C ゲームC
Canvas.GAME_D ゲームD
ゲームA・ゲームB・ゲームC・ゲームDの4つのキー定数は、Vアプリでは利用しません。

数字キーと記号キー(#キー・*キー)は、getGameAction()メソッドは通さず、直接キーコード定数と比較します。Canvasクラスで持っているキーコード定数は次の12個です。
キーコード定数
Canvas.KEY_NUM0 0キー
Canvas.KEY_NUM1 1キー
Canvas.KEY_NUM2 2キー
Canvas.KEY_NUM3 3キー
Canvas.KEY_NUM4 4キー
Canvas.KEY_NUM5 5キー
Canvas.KEY_NUM6 6キー
Canvas.KEY_NUM7 7キー
Canvas.KEY_NUM8 8キー
Canvas.KEY_NUM9 9キー
Canvas.KEY_POUND #キー
Canvas.KEY_STAR *キー

今回は、選択キーを押した時に音声認識の開始処理を行うので、次のようになります。
VoiceRecogCanvas.javaの一部
public void keyPressed(int keyCode) {
    int action=getGameAction(keyCode);

    //音声認識を開始
    if (action==FIRE) {
        <<音声認識の開始処理>>
    }
}


辞書の生成
Vアプリの音声認識機能は、辞書として登録された単語の中から、どの単語に一番近いかを判断します。辞書を用意するには、VoiceRecognitionDictionaryクラスのオブジェクトを生成する必要があります。
VoiceRecogCanvas.javaの一部
private VoiceRecognitionDictionary dict;
VoiceRecogCanvas.javaの一部
dict=new VoiceRecognitionDictionary();

辞書に単語を追加するには、setWord()メソッドを使います。
void setWord(int wordNo,String word)
wordNo:単語番号
word :単語
単語番号は単語を識別するための番号で、既に同じ番号が設定されている時は上書きされます。設定可能な値は1から9999で、0からでない点に注意してください。追加する単語は半角カタカナで記述し、1単語20文字、100単語500文字まで可能です。

今回はString型配列DICT_WORDの値を追加するので、次のようになります。
VoiceRecogCanvas.javaの一部
private final static String DICT_WORD[]={  //単語定数
    null,"オハヨウ","コンニチワ","コンバンワ"};
VoiceRecogCanvas.javaの一部
for (int i=1;i<DICT_WORD.length;i++) {
    dict.setWord(i,DICT_WORD[i]);
}


音声認識を開始
辞書が用意できたら、音声認識を開始します。まず、音声認識の処理を行うVoiceRecognitionオブジェクトをVoiceRecognitionクラスのgetInstance()で取得してください。取得できたら、setVoiceRecognitionListener()メソッドで音声認識イベントの通知先を指定します。
void setVoiceRecognitionListener(VoiceRecognitionListener listener)
listener:音声認識リスナー
今回は、VoiceRecogCanvasクラス自身が音声認識リスナーになるので、thisをセットします。

VoiceRecogCanvas.javaの一部
private VoiceRecognition recog; //音声認識
VoiceRecogCanvas.javaの一部
recog=VoiceRecognition.getInstance();
recog.setVoiceRecognitionListener(this);

音声認識を開始するには、recognize()メソッドを使います。
void recognize(VoiceRecognitionDictionary dict,int voiceOffTimeOut,int voiceOnTimeOut,int maxCandidate)
dict:辞書
voiceOffTimeOut:音声入力がない時のタイムアウト時間(10〜10000ミリ秒)
voiceOnTimeOut:音声入力がある時のタイムアウト時間(10〜10000ミリ秒)
maxCandidate:認識結果の取得候補数(1〜20)

今回は音声がない時とある時のタイムアウト時間に4秒(4000ミリ秒)、認識結果の取得候補数に3を指定するので、次のようになります。
VoiceRecogCanvas.javaの一部
recog.recognize(dict,4000,4000,3);


音声認識リスナーの実装
音声認識リスナーは、音声認識のイベント情報を受信するためのリスナーです。今回は、VoiceRecogCanvasクラス自身にリスナーのインタフェースを実装します。
VoiceRecogCanvas.javaの一部
class VoiceRecogCanvas extends Canvas
    implements VoiceRecognitionListener {

音声認識リスナーには、次の3つのメソッドが必要です。

void recognitionStarted()
音声認識を開始した時に呼ばれるメソッドです。今回は、"音声認識中…"という文字列を表示しています。
VoiceRecogCanvas.javaの一部
public void recognitionStarted() {
    //情報の表示
    info=new String[]{"音声認識中…"};
    repaint();
}


void recognized(int num)
音声認識が成功した時に呼ばれるメソッドです。引数numには認識した単語の数が渡されます。認識結果から指定の順位の語句番号を取得するには、getCandidateWord()メソッドを使います。
int getCandidateWord(int order)
order:語句番号

認識結果から指定の順位の認識スコアを取得するには、getCandidateScore()メソッドを使います。
int getCandidateScore(int order)
order:語句番号

今回は、辞書に登録されている言葉のうち、発音が近いと認識した順に3つを、認識スコアといっしょに表示しています。
VoiceRecogCanvas.javaの一部
public void recognized(int num) {
    //情報の表示
    info=new String[num];
    for (int i=1;i<=num;i++) {
        info[i-1]=DICT_WORD[recog.getCandidateWord(i)]+
            "["+recog.getCandidateScore(i)+"]";
    }
    repaint();
}


void recognitionFailed(int reason)
音声認識を失敗した時に呼ばれるメソッドです。引数reasonには失敗理由を示す数値が渡されます。

1:無声時間タイムアウト
2:音声入力タイムアウト
3:その他のエラー
4:音声認識の開始失敗

今回は、"音声認識失敗"と表示しています。
VoiceRecogCanvas.javaの一部
public void recognitionFailed(int reason) {
    //情報の表示
    info=new String[]{"音声認識失敗"};
    repaint();
}


マニフェストファイルとJADファイル
VアプリでJSCL1.2の機能を使った時は、マニフェストファイルとJADファイルに「MIDlet-OCL: JSCL-1.2.0」の1行を追加する必要があります。
MANIFEST.MF
MIDlet-Name: VoiceRecogEx
MIDlet-Vendor: NPAKA
MIDlet-Version: 1.0
MIDlet-OCL: JSCL-1.2.0
MIDlet-1: VoiceRecogEx,,VoiceRecogEx
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-1.0
VoiceRecogEx.jad
MIDlet-Name: VoiceRecogEx
MIDlet-Vendor: NPAKA
MIDlet-Version: 1.0
MIDlet-OCL: JSCL-1.2.0
MIDlet-Jar-Size: 2277
MIDlet-Jar-URL: VoiceRecogEx.jar


エミュレータでの実行
2003年12月現在、P5型用の開発ツールは一般公開されていますが、エミュレータは提供されていません。そのため、実機で直接動作テストする必要があります。


★こたえて!そらみを作る★


それでは、本題の音声認識アプリ「こたえて!そらみ」を作ります。アプリを起動すると画面にそらみが現れます。決定キーを押すとそらみが「なに?」と聞いてくるので、話し掛けてあげてください。その言葉に応じて、そらみが返答してくれます。


言葉 返答
オハヨウ おっはー!
コンニチワ こんちわ〜
コンバンワ こんばんわぁ!

今回のプログラムは、次の2つのクラスで構成されています。

画像ファイルの用意
Vアプリで利用できる画像ファイル形式は256色以下のインデックスカラーPNG(PNG-8)とJPEGです。今回は次の3枚のPNGファイルを使います。JARファイルに含めてください。


・そらみ(待ち状態)

-0.png
-240x260




・そらみ(聞き状態)

-1.png
-240x260




・そらみ(会話状態)

-2.png
-240x260




サウンドファイルの用意
Vアプリで利用できるサウンドファイルは、SMD(*.smd,*.smz,*.smx)、SMAF/MA2(*.mmf)、SMAF/MA3(*.mmf)、SMF/Phrase(*.spf)です。今回は次の4つのサウンドファイルを使います。JARファイルに含めてください。

・なに?
-0.mmf

・おっはー!
-1.mmf

・こんちわ〜
-2.mmf

・こんばんわぁ!
-3.mmf


KotaSoraクラス
KotaSoraクラスは、プログラムの本体となるクラスです。
KotaSora.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

//こたえて!そらみ(本体)
public class KotaSora extends MIDlet {

    //コンストラクタ
    public KotaSora() {
        Display.getDisplay(this).setCurrent(new KotaCanvas());
    }

    //アプリの開始
    public void startApp() {
    }

    //アプリの一時停止
    public void pauseApp() {
    }

    //アプリの終了
    public void destroyApp(boolean unconditional) {
    }
}


KotaCanvasクラス
KotaCanvasクラスは、キャンバスとなるクラスです。
KotaCanvas.java
import com.jblend.media.smaf.*;
import com.jblend.media.*;
import com.j_phone.io.*;
import javax.microedition.lcdui.*;

//こたえて!そらみ(キャンバス)
class KotaCanvas extends Canvas
    implements VoiceRecognitionListener,MediaPlayerListener {
    private final static String DICT_WORD[]={  //単語定数
        null,"オハヨウ","コンニチワ","コンバンワ"};
    private VoiceRecognitionDictionary dict;   //辞書
    private VoiceRecognition recog;            //音声認識

    private final static int S_WAIT=0,S_HEAR=1,S_TALK=2;//状態定数
    private int        state =S_WAIT;          //状態
    private Image[]    image =new Image[3];    //イメージ
    private SmafPlayer player=new SmafPlayer();//プレイヤー
    private SmafData[] sound =new SmafData[4]; //サウンド

    //コンストラクタ
    KotaCanvas() {
        try {
            //イメージの読み込み
            for (int i=0;i<3;i++) {
                image[i]=Image.createImage("/"+i+".png");
            }

            //サウンドの読み込み
            for (int i=0;i<4;i++) {
                sound[i]=new SmafData("resource:"+i+".mmf");
            }
        } catch (Exception e) {
        }
    }


    //描画
    public void paint(Graphics g) {
        if (image[state]!=null) {
            g.drawImage(image[state],0,0,g.LEFT|g.TOP);
        }
    }

    //キープレスイベント
    public void keyPressed(int keyCode) {
        int action=getGameAction(keyCode);

        //聞き状態へ
        if (state==S_WAIT && action==FIRE) {
            try {
                //サウンドの再生
                player.setData(sound[0]);
                player.play();
                player.addMediaPlayerListener(this);
            } catch (Exception e) {
            }
        }
    }

    //プレイヤーの状態変化を通知
    public void playerStateChanged(MediaPlayer player) {
        //聞き状態へ
        if (state==S_WAIT) {
            try {
                //辞書の生成
                dict=new VoiceRecognitionDictionary();
                for (int i=1;i<DICT_WORD.length;i++) {
                    dict.setWord(i,DICT_WORD[i]);
                }

                //音声認識を開始
                recog=VoiceRecognition.getInstance();
                recog.setVoiceRecognitionListener(this);
                recog.recognize(dict,4000,4000,3);

                //状態の表示
                state=S_HEAR;
                repaint();
            } catch (Exception e) {
            }
        }
        //待ち状態へ
        else {
            //状態の表示
            state=S_WAIT;
            repaint();
        }
        //メディアプレイヤーリスナーの解除
        player.removeMediaPlayerListener(this);
    }

    //プレイヤーの繰り返し再生を通知
    public void playerRepeated(MediaPlayer player) {
    }

    //音声認識開始を通知
    public void recognitionStarted() {
    }

    //音声認識完了を通知
    public void recognized(int num) {
        try {
            if (num>=1 && recog.getCandidateScore(1)>=150) {
                //サウンドの再生
                player.setData(sound[recog.getCandidateWord(1)]);
                player.play();
                player.addMediaPlayerListener(this);

                //状態の表示
                state=S_TALK;
                repaint();
            } else {
                //状態の表示
                state=S_WAIT;
                repaint();
            }
        } catch (Exception e) {
        }
    }

    //音声認識失敗を通知
    public void recognitionFailed(int reason) {
        //状態の表示
        state=S_WAIT;
        repaint();
    }
}


com.jblend.media.smafパッケージとcom.jblend.mediaパッケージ
サウンドの再生を行うためのクラスは、com.jblend.media.smafパッケージとcom.jblend.mediaパッケージに含まれているので、importしてください。
KotaCanvas.javaの一部
import com.jblend.media.smaf.*;
import com.jblend.media.*;


状態の遷移
今回のアプリには、次の2つの状態があります。現在の状態はint型変数stateで保持しています。
KotaCanvas.javaの一部
private final static int S_WAIT=0,S_HEAR=1,S_TALK=2;//状態定数
KotaCanvas.javaの一部
private int state =S_WAIT; //状態


イメージの読み込みと描画
ImageクラスのcreateImage()メソッドで画像ファイルを読み込みます。ファイル名には「/0.gif」のように頭に"/"をつけてください。
KotaCanvas.javaの一部
for (int i=0;i<3;i++) {
    image[i]=Image.createImage("/"+i+".png");
}

GraphicsクラスのdrawImage()メソッドでイメージをキャンバスに描画します。
void drawImage(Image image,int x,int y,int align)
image:表示するイメージ
x :X座標
y :Y座標
align:配置

align引数には配置定数を指定します。指定した座標に文字列の左上を配置したい時は"Graphics.LEFT|Graphics.UP"を指定します。
配置定数
Graphics.TOP
Graphics.BOTTOM
Graphics.LEFT
Graphics.RIGHT
Graphics.HCENTER 水平中央
Graphics.VCENTER 垂直中央

今回は、state変数に対応したイメージを表示するので、次のようになります。
KotaCanvas.javaの一部
if (image[state]!=null) {
    g.drawImage(image[state],0,0,g.LEFT|g.TOP);
}


サウンドの読み込みと再生
SMAFファイルを読み込むには、SmafDataクラスを使います。コンストラクタは次の通りです。
SmafData(String location)
location:読み込み先
読み込み先を次のフォーマットで指定します。
resource:ファイル名

今回は、0.mmf〜3.mmfを読み込むので次のようになります。
KotaCanvas.javaの一部
for (int i=0;i<4;i++) {
    sound[i]=new SmafData("resource:"+i+".mmf");
}

サウンドを鳴らすのにはSmafPlayerクラスを使います。SmafPlayerクラスのオブジェクトを生成し、setData()メソッドでSmafDataオブジェクトをセットし、play()メソッドで再生、stop()メソッドで停止します。
void setData(SmafData data)
data:SmafDataオブジェクト

今回は、音声認識完了を通知された時、認識スコアが150以上の時に、対応するサウンドを再生するので、次のようになります。
KotaCanvas.javaの一部
try {
    if (num>=1 && recog.getCandidateScore(1)>=150) {
        //サウンドの再生
        player.setData(sound[recog.getCandidateWord(1)]);
        player.play();
        player.addMediaPlayerListener(this);

        //状態の表示
        state=S_TALK;
        repaint();
    } else {
        //状態の表示
        state=S_WAIT;
        repaint();
    }
} catch (Exception e) {
}


マニフェストファイルとJADファイル
QVGA端末(画面解像度の大きな端末)では、昔の小さな画面解像度向けに作られたアプリに対応するために、縦横2倍で拡大表示されます。240x260ドットの高解像度なアプリを作りたい時は、マニフェストファイルとJADファイルに「MIDlet-Application-Range: 0,0」の1行を追加する必要があります。
J-SH53の画面サイズとフォントサイズ
画面サイズ フォントLARGE フォントMEDIUM フォントSMALL
通常モード 120x130 20x19 12x12 12x12
高詳細モード 240x260 20x19 20x19 12x12

また、マニフェストファイルやJADファイルで日本語を使った時は、UTF-8エンコードで保存するようにしてください。UTF-8では全角1文字が2バイトとは限らないので、文字列サイズの制限に注意してください。
MANIFEST.MF
MIDlet-Name: こたえて!そらみ
MIDlet-Vendor: NPAKA
MIDlet-Version: 1.0
MIDlet-OCL: JSCL-1.2.0
MIDlet-Application-Range: 0,0
MIDlet-1: KotaSora,,KotaSora
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-1.0
KotaSora.jad
MIDlet-Name: こたえて!そらみ
MIDlet-Vendor: NPAKA
MIDlet-Version: 1.0
MIDlet-OCL: JSCL-1.2.0
MIDlet-Application-Range: 0,0
MIDlet-Jar-Size: 84401
MIDlet-Jar-URL: KotaSora.jar


★おわりに★


次回は、パケ代定額で話題沸騰のEV-DO端末で動くEZアプリを作る予定です。お楽しみに。



−戻る−


(C)Npaka/Sehira, 2003-2004