家電を音声でコントロールする。

ラナルータやライティングを使いたくなるときってありませんか?
蛍光灯に手を飛ばして、「光りあれ」と叫んでみたくなりませんか?
i-remocon + windows音声認識 + リモコン を利用すれば、誰でも呪文を唱えることができます。



i-リモコンのtelnet インターフェースが公開されたので、
音声で家電をコントロールするシステムを作ってみました。
うちの設定ファイルとソースコードはここにあるので適当にもっていって、自分家の設定にカスタマイズして遊びましょう。


遊ぶためのサンプルとか一式をダウンロードする。

NEW-遊ぶためのサンプルとか一式をダウンロードする。

windows speech appi(SAPI)

windows xp 以降は音声認識がOSに標準で搭載されました。
それをCOM経由で呼び出して、利用しています。

コマンドは _vicecommand.xml ってファイルに書きます。
この xmlvoicexmlみたいな表記ができるようでできない Microsoft の SAPI の xml です。

    <P valstr="実行したいコマンド">/適当な名前/読み方をひらがなで書く;</P>

設定例
<?xml version="1.0" encoding="UTF-8"?>
<GRAMMAR>
<RULE name="S" toplevel="ACTIVE">
  <L>
    <P>
      <RULEREF name="NAME"/>
    </P>
  </L>
</RULE>
<RULE name="NAME" toplevel="INACTIVE">
  <L propname="NAME">
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコンOFF.ttl">/エアコンOFF/えあこんおふ;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコンOFF.ttl">/エアコン切って/えあこんきって;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン冷房19度.ttl">/エアコンMAX/えあこんまっくす;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン暖房27度.ttl">/暖房MAX/だんぼうまっくす;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン冷房25度.ttl">/エアコンつけて/えあこんつけて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン暖房25度.ttl">/暖房つけて/だんぼうつけて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン温度上げる.ttl">/エアコン温度上げて/えあこんのおんどをあげて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン温度下げる.ttl">/エアコン温度下げて/えあこんのおんどをさげて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン温度上げる.ttl">/温度上げて/おんどあげて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン温度下げる.ttl">/温度下げて/おんどさげて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン温度上げる.ttl">/温度上げて/さむい;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン温度下げる.ttl">/温度下げて/あつい;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン温度上げる.ttl">/温度上げて/ちょっとさむいかな;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エアコン温度下げる.ttl">/温度下げて/ちょっとあついかな;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\液晶を消す.ttl">/液晶消して/えきしょうつけて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\液晶をつける.ttl">/液晶つけて/えきしょうつけて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気をつける.ttl">/電気つけて/でんきつけて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気をつける.ttl">/でんき/でんき;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気をつける.ttl">/しょうめい/しょうめい;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気をつける.ttl">/しょうめいをつけて/しょうめいつけて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気をつける.ttl">/レミーラ /れみーら;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気をつける.ttl">/主は申された、光りあれ/しゅはもうされた、ひかりあれ;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気をつける.ttl">/光りあれ/ひかりあれ;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気をつける.ttl">/光りよ/ひかりよ;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気をつける.ttl">/ライティング/らいてぃんぐ;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気を消す.ttl">/電気をけして/でんきけして;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気を消す.ttl">/しょうめいをけして/しょうめいけして;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気を消す.ttl">/くらやみに/くらやみに;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気を消す.ttl">/くらやみよ/くらやみよ;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気を消す.ttl">/イン・ザ・ダーク/いんざだーく;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\電気を消す.ttl">/イン・ザ・ダーク/いんざだーくねす;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\ギャラクシー.ttl">/ギャラクシー/ぎゃらくしー;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エターナルダーク.ttl">/エターナルダーク/えたーなるだーく;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\エターナルダーク.ttl">/エターナルダークネス/えたーなるだーくねす;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\ラナルータ.ttl">/ラナルータ/らなるーた;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\バルス.ttl">/最終奥義バルス/さいしゅうおうぎばるす;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\バルス.ttl">/エターナルフォースブリザード/えたーなるふぉーすぶりざーど;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\ほむほむりせっと.ttl">/基本姿勢/きほんしせい;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\ほむほむりせっと.ttl">/環境リセット/かんきょうりせっと;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\ほむほむりせっと.ttl">/ほむほむりせっと/ほむほむりせっと;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\プロジェクターをつける.ttl">/プロジェクター起動/ぷろじぇくたーきどう;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\プロジェクターをつける.ttl">/プロジェクターつけて/ぷろじぇくたーつけて;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\プロジェクターを消す.ttl">/プロジェクター消して/ぷろじぇくたーけして;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\シアターモード暗い.ttl">/シアターモード/しあたーもーど;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\シアターモード暗い.ttl">/暗いシアターモード/くらいしあたーもーど;</P>
    <P valstr="C:\Users\rti\Desktop\tool\家電\シアターモード明るい.ttl">/明るいシアターモード/あかるいしあたーもーど;</P>
    <P valstr="C:\Users\rti\AppData\Local\Google\Chrome\Application\chrome.exe">/chrome/くろむ;</P>
    <P valstr="http://twitter.com/">/twitter/ついったー;</P>
    <P valstr="https://mail.google.com/mail/">/gmail/じーめーる;</P>
    <P valstr="http://www.nicovideo.jp/ranking">/niconico/にこにこ;</P>
    <P valstr="C:\Program Files (x86)\teraterm\ttermpro.exe">/ssh/えすえすえいち;</P>
    <P valstr="C:\Users\rti\Desktop\イオナズン.avi">/イオナズン/いおなずん;</P>
    <P valstr="C:\Users\rti\Desktop\イオナズン.avi">/イオナズン/これがよのめらじゃ;</P>
    <P valstr="C:\Users\rti\Desktop\スターライトブレーカー.avi">/スターライトブレーカー/すたーらいとぶれいかー;</P>
    <P valstr="C:\Users\rti\Desktop\スターライトブレーカー.avi">/これがわたしのぜんりょくぜんかい/これがわたしのぜんりょくぜんかい;</P>
    <P valstr="QUIT">/えんいー/えんいー;</P>
  </L>
</RULE>
</GRAMMAR>


みたいにすると、動きます。
実行したいコマンドのパスは自分家の設定で適当に変えてください。
ttl ファイルは teraterm pro のマクロです。
これも自分の家の設定に従って変えてください。

遊び方

naichichi.exe を実行すると、_vicecommand.xml を SAPI に読み込ませます。
あとは、自動的に音声認識が開始されるので(たまにされないときもある・・・原因不明)、マイクに向かってコマンドを読み上げます。

すると、それに対応したコマンドが実行されます。
あとは、痛い厨二病な呪文をたくさん書きましょう。そして、大きな声ではっきりと読み上げましょう。


家電がコントロールされます。
やったー。

そして、叫んだ呪文の恥ずかしさに打ちひしがれます。

技術解説

COMの操作はそうゆーもんだということでやるしかないですね。
一応、windows sdk にサンプル (C:\Program Files\Microsoft SDKs\Windows\v7.1\Samples\winui\speech 以下) があります。

//RSpeechRecognition.h より抜粋
class RSpeechRecognition  
{
public:
	RSpeechRecognition();
	virtual ~RSpeechRecognition();
	//音声認識のためのオブジェクトの構築.
	void RSpeechRecognition::Create(const string & inToWave = "") throw(RComException);
	void RSpeechRecognition::Create(const string & inToWave = "",const string & inGrammarXML = "") throw(RComException);

	//認識開始
	void RSpeechRecognition::Listen() throw(RComException);

	const string & getResultString() const
	{
		return ResultString;
	}
	const string  getResultMap(const string & inKey) const
	{
		RStringMap::const_iterator i = ResultMap.find(inKey);
		if (i == ResultMap.end() ) return "";

		return (*i).second;
	}
private:
	CComPtr<ISpRecognizer>		Engine;			// 認識エンジンオブジェクト
	CComPtr<ISpRecoContext>		RecoCtxt;

	
	CComPtr<ISpRecoGrammar>     DictationGrammar;	//メインとなる文法


	//認識結果
	string			ResultString;
	RStringMap		ResultMap;
};



//RSpeechRecognition.cpp より抜粋

void RSpeechRecognition::Create(const string & inToWave) throw(RComException)
{
	string dummy = "";
	this->Create(inToWave,dummy);
}

//音声認識のためのオブジェクトの構築.
void RSpeechRecognition::Create(const string & inToWave,const string & inGrammarXML) throw(RComException)
{
	USES_CONVERSION;

	HRESULT hr;

	// 認識エンジンオブジェクトの作成
	//	CLSID_SpSharedRecognizer		マイクから録音するとき?
	//	CLSID_SpInprocRecognizer		Waveから変換するとき?
	
	if ( inToWave.empty() )
	{
		hr = this->Engine.CoCreateInstance(CLSID_SpSharedRecognizer);
		if(FAILED(hr))	throw RComException(hr , "CLSID_SpSharedRecognizer 構築 に失敗");
	}
	else
	{
		CComPtr<ISpStream> cpStream;

		hr = this->Engine.CoCreateInstance(CLSID_SpInprocRecognizer);
		if(FAILED(hr))	throw RComException(hr , "CLSID_SpInprocRecognizer 構築 に失敗");

		hr = cpStream.CoCreateInstance(CLSID_SpStream);
		if(FAILED(hr))	throw RComException(hr , "CoCreateInstance  CLSID_SpStream に失敗");

		hr = cpStream->BindToFile( A2W( inToWave.c_str() ) , SPFM_OPEN_READONLY , NULL , NULL,  SPFEI_ALL_EVENTS);  
	    if(FAILED(hr))	throw RComException(hr , "BindToFile に失敗");

		hr = this->Engine->SetInput( cpStream, TRUE);  
	    if(FAILED(hr))	throw RComException( this->Engine , CLSID_SpSharedRecognizer , hr , "SetInput に失敗");
	}

	// 認識コンテクストオブジェクトの作成
	hr = this->Engine->CreateRecoContext(&this->RecoCtxt);
	if(FAILED(hr))	throw RComException(hr , "CreateRecoContext に失敗");

	hr = this->RecoCtxt->SetNotifyWin32Event();
	if ( FAILED(hr) )	throw RComException(hr , "SetNotifyWin32Event に失敗");

	hr = this->RecoCtxt->SetInterest(SPFEI(SPEI_RECOGNITION), SPFEI(SPEI_RECOGNITION));
	if ( FAILED(hr) )	throw RComException(hr , "SetInterest に失敗");

	hr = this->RecoCtxt->SetAudioOptions(SPAO_RETAIN_AUDIO, NULL, NULL);
	if ( FAILED(hr) )	throw RComException(hr , "SetAudioOptions に失敗");


	//メインとなる文法の作成
	hr = this->RecoCtxt->CreateGrammar(0, &this->DictationGrammar);
	if ( FAILED(hr) )	throw RComException(hr , "CreateGrammar に失敗");

	hr = this->DictationGrammar->LoadDictation(NULL, SPLO_STATIC);
	if ( FAILED(hr) )	throw RComException(hr , "LoadDictation に失敗");

	if ( inGrammarXML.empty() )
	{
		//録音開始
		hr = this->DictationGrammar->SetDictationState( SPRS_ACTIVE );
		if ( FAILED(hr) )	throw RComException(hr , "SetDictationState に失敗");
	}
	else
	{
		//ユーザ指定ファイルからのロード
		hr = this->DictationGrammar->LoadCmdFromFile( A2W( inGrammarXML.c_str() ) ,SPLO_STATIC);
		if ( FAILED(hr) )	throw RComException(hr , "LoadCmdFromFile に失敗");

		//録音開始
		hr = this->DictationGrammar->SetRuleState(NULL, NULL, SPRS_ACTIVE );
		if ( FAILED(hr) )	throw RComException(hr , "SetRuleState に失敗");
	}
}

//認識開始
void RSpeechRecognition::Listen() throw(RComException)
{
	USES_CONVERSION;
	HRESULT hr;

	CSpEvent event;

	//録音が終わるまで大待機
	hr = this->RecoCtxt->WaitForNotifyEvent(INFINITE);
	if ( FAILED(hr) )	throw RComException(hr , "WaitForNotifyEvent に失敗");

	hr = event.GetFrom( this->RecoCtxt );
	if ( FAILED(hr) )	throw RComException(hr , "GetFrom に失敗");

	//認識した結果
	ISpRecoResult* result;
	result = event.RecoResult();

	//認識した文字列の取得
	CSpDynamicString dstrText;
	hr = result->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &dstrText, NULL);
	if ( FAILED(hr) )	throw RComException(hr , "録音したテキストの取得に失敗しました");
	this->ResultString = W2A(dstrText);

	//認識に XMLを使用した場合、代入された結果を得る.
	SPPHRASE *pPhrase;
	event.RecoResult()->GetPhrase(&pPhrase);
	const SPPHRASEPROPERTY *pProp;
	for (pProp = pPhrase->pProperties; pProp; pProp = pProp->pNextSibling)
	{
		string a = W2A(pProp->pszName);
		this->ResultMap[ W2A(pProp->pszName) ] = W2A(pProp->pszValue);
	}
	CoTaskMemFree(pPhrase);

/*
//デバッグのため、読み取った音声をwaveファイルに保存してみる。
	//ファイルに保存. save
	{
		CComPtr<ISpStreamFormat>	ResultStream;

		CComPtr<ISpVoice> voice;
		hr = this->RecoCtxt->GetVoice(&voice);
		if(FAILED(hr))	throw RComException(hr , "GetVoice に失敗");

		hr = event.RecoResult()->GetAudio( 0, 0, &ResultStream );
		if ( FAILED(hr) )	throw RComException(hr , "GetAudio に失敗しました");
		{
			CComPtr<ISpStream> cpWavStream; 
			CComPtr<ISpStreamFormat> cpOldStream; 
			CSpStreamFormat OriginalFmt; 
			voice->GetOutputStream( &cpOldStream ); 
			OriginalFmt.AssignFormat(cpOldStream); 
			hr = SPBindToFile( L"C:\\Users\\rti\\Desktop\\naichichi\\test\\output.wav",SPFM_CREATE_ALWAYS, 
				&cpWavStream,&OriginalFmt.FormatId(), 
				OriginalFmt.WaveFormatExPtr() 	); 
			voice->SetOutput(cpWavStream,TRUE); 
		}
	}
*/
}

なんでないちち?

SAPIで遊んでいたときに、ないちち放送局〜ってな番組が流行っていたからw
今から何年前だろう、、、なつかしいネ。


現代技術で偽春菜やるともっといろいろなことができる気がするんだけどね。
\e

(追記)
これから、一年の研究開発を得て、誰でも音声認識で家電が操作できるガジェットを作りました。ないちちも naichichi2として linux上で活躍しています。
音声認識で家電を操作するフューチャーホームコントローラーについて