littlewing

人間とコンピューターとメディアの接点をデザインするために考えたこと

ドコモAPIを使った音声合成を使ってみる

  • この記事は書きかけです。

ドコモからスマートフォンで利用できるAPIが公開されているので、使ってみます。

顔・画像・音声(入力・出力)など様々なAPIがあるのですが、今回は音声合成のAPIを利用してみます。

APIの概要 | docomo Developer support | NTTドコモ

注意事項

  • 利用には利用規約の同意とアカウント登録が必要です。(Facebook/Googleアカウントでのログイン可能)
  • テスト版は簡単な申請で、即日利用可能ですが、本番で利用するには審査がある場合があります。

音声合成を使う

音声合成APIは

  • スマホ用SDKの「音声合成 SDK (Powered by エーアイ)」
  • WEB APIの音声合成【Powered by HOYAサービス】
  • WEB APIの【Powered by NTTアイティ】音声合成API の3種類があります。

音声合成 | docomo Developer support | NTTドコモ

HOYAの音声合成WEBAPI

  • 以下の感じで試すことができます。 *「XXXXXXXX」の部分は発行されたAPIKEYに置き換えてください。
  • iOSとPCのChromeで動くのは確認できましたが、AndroidとIEでは音声は再生されません。
  • PC版Chromeでは、タッチなしで音声再生が可能ですが、iOSではタッチイベントがないと、audio.play();が実行されないようです。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD> 
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
</HEAD>
<BODY>
<script>
jQuery(function($) {
    xhr = new XMLHttpRequest();
    xhr.open('POST', 'https://api.apigw.smt.docomo.ne.jp/voiceText/v1/textToSpeech?APIKEY=XXXXXXXX', true);

    xhr.responseType = 'arraybuffer'
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
    var datax = "text=しゃべる内容&speaker=hikari&emotion=happiness&emotion_level=2&format=ogg";
    
    xhr.onload = function (e) {
        if( this.status == 200){
            view = new Uint8Array(this.response);
            blob = new Blob([view], { "type" : "audio/wav" });
            URL = window.URL || window.webkitURL;

            audio = new Audio(URL.createObjectURL(blob));
            //audio.play();
        }
    };
    data = datax;
    xhr.send(data);
});

function sound(){
alert("start");
            audio.play();
}
</script>
    <p><a onClick="sound()">このテキストを押すと挨拶します!</a></p>

</BODY>
</HTML>

【Powerd by エーアイ】音声合成 SDK for iOS v1.0.1をつかう

サンプルプロジェクトの実行

  1. SDKをダウンロード(AITalk音声合成SDK_for_iOS_1.0.1)
  2. sampleフォルダのaiTalkSample.zipを解凍
  3. aiTalkSample.xcodeprojをXcodeで開く
  4. ViewController.mの
NSString * const APIKEY = @"";

にAPIKEYを設定

5ビルドすると以下のような画面が表示され、音声が再生されます。

実際のプロジェクトに組み込んでみる

  1. docomoAPIというグループを作成
  2. その中に、/inc/と/libs/フォルダをドラッグ reference ではなく、new Groupにする!
  3. 利用するファイルでヘッダをインポート
//ドコモ音声合成APIヘッダー
#import "AiTalkError.h"
#import "AiTalkProsody.h"
#import "AiTalkSsml.h"
#import "AiTalkTextToSpeech.h"
#import "AiTalkVoice.h"
#import "AiTalkVoiceBase.h"
#import "AuthApiKey.h"
#import "SdkError.h"
    • (void)viewDidLoadあたりで、APIKEYを利用して音声合成を初期化
    // 開発者ポータルから取得したAPIキーの設定
    [AuthApiKey initializeAuth:@"XXXXXX"];
  • その後こんな感じ
    
    //音声再生
    
    AiTalkSsml * ssml = [[AiTalkSsml alloc]init];
    AiTalkVoice * voice = [[AiTalkVoice alloc]initWithVoiceName:@"nozomi"];
    [voice addText:[NSString stringWithFormat:@"のこり あと%dスタンプです。",placeDictionaryElementsNum - getStampNum]];
    [ssml addVoice:voice];
    AiTalkTextToSpeech * search = [[AiTalkTextToSpeech alloc]init];
    AiTalkError *sendError =
    [search requestAiTalkSsmlToSound:[ssml makeSsml]
                          onComplete:^(NSData *data)
     {
         NSLog(@"onComplete");
         [self playAudio:data];
     } onError:^(SdkError *receiveError) {
         [self onError:receiveError];
     }];
    if(sendError){
        [self onError:sendError];
    }

上記実行するには別途、サンプルプロジェクトの

  • -(void)onError:(NSError *)error
  • -(void)playAudio:(NSData *)data
  • -(Byte *)setHeader:(long)dataLength
  • -(NSData)addHeader:(NSData)data

をコピーするなどして持ってくる必要があります。

Androidでも動かす

  • やったのはMac+AndroidStudio
  • Android版のサンプルプロジェクトはそのままではビルドできない

サンプルの実行のためにやったこと

エラー: libpng error: Not a PNG file

  • ic_launcher.png を別のPNGファイルで上書き(3箇所)

エラー: Error:(1, 1) \65279 は不正な文字です。

  • MainActivity.javaをUTF-8(BOM無し)で保存し直し

エラー: Error:duplicate files during packaging of APK /Users/***/AndroidStudioProjects/AitalkSample/app/build/outputs/apk/app-debug-unaligned.apk

  • エラー内容になるように app/build.gradleに以下を記述
android {

    packagingOptions {
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/NOTICE'
    }

}

手元のプロジェクトに組み込んでみる

import jp.ne.docomo.smt.dev.common.exception.SdkException;
import jp.ne.docomo.smt.dev.common.exception.ServerException;
import jp.ne.docomo.smt.dev.aitalk.AiTalkTextToSpeech;
import jp.ne.docomo.smt.dev.aitalk.data.AiTalkSsml;
import jp.ne.docomo.smt.dev.common.http.AuthApiKey;
  • Classの先頭で宣言
    // 音声文字列SSML
    private AiTalkSsml ssml;
    // 非同期タスク
    private AitestAsyncTask task;
    // 警報ダイアログ
    private AlertDialog.Builder dlg;


    static final String APIKEY ="xxxxx";
  • onCreateで呼び出す
        // API キーの登録
        AuthApiKey.initializeAuth(APIKEY);
        try{
            // SSMLテキスト作成
            ssml = new AiTalkSsml();
            ssml.startVoice("nozomi");
            ssml.addText("こんにちは");
            ssml.endVoice();

            // 音声変換を実行し、音声を出力する。
            // メインスレッドではHTTP通信できないので別スレッドで
            task = new AitestAsyncTask (dlg,AitestAsyncTask.henkan_ssml_sound);
            task.execute(ssml);


        }catch (Exception ex){
            Log.e(TAG, ex.toString());
        }
        // 非同期タスクのバックグラウンド実行部分
        @Override
        protected byte[] doInBackground(Object... params) {
            byte[] resultData = null;
            try {
                // 要求処理クラスを作成
                AiTalkTextToSpeech search = new AiTalkTextToSpeech();
                // 要求処理クラスにリクエストデータを渡し、レスポンスデータを取得する
                switch (_henkan){
                    case henkan_ssml_sound:
                        resultData = search.requestAiTalkSsmlToSound(((AiTalkSsml)params[0]).makeSsml());
                        break;
                    case henkan_ssml_aikana:
                        resultData = search.requestAiTalkSsmlToAikana(((AiTalkSsml)params[0]).makeSsml()).getBytes();
                        break;
                    case henkan_ssml_jeitakana:
                        resultData = search.requestAiTalkSsmlToJeitakana(((AiTalkSsml)params[0]).makeSsml());
                        break;
                    case henkan_aikana_sound:
                        resultData = search.requestAikanaToSound((String)params[0]);
                        break;
                    case henkan_aikana_jeitakana:
                        resultData = search.requestAikanaToJeitakana((String)params[0]);
                        break;
                    case henkan_jeitakana_sound:
                        resultData = search.requestJeitakanaToSound(((String)params[0]).getBytes("Shift_Jis"));
                        break;
                    default:
                        return null;
                }
                // 音声変換の場合は、スピーカに出力
                switch (_henkan){
                    case henkan_ssml_sound:
                    case henkan_aikana_sound:
                    case henkan_jeitakana_sound:
                        // 音声出力用バッファ作成
                        int bufSize = AudioTrack.getMinBufferSize(16000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
                        // ビッグエディアンをリトルエディアンに変換
                        search.convertByteOrder16(resultData);
                        // 音声出力
                        AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, 16000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, bufSize, AudioTrack.MODE_STREAM);
                        at.play();
                        at.write(resultData, 0, resultData.length);
                        // 音声出力待ち
                        Thread.sleep(resultData.length/32);
                        break;
                }
            } catch (SdkException ex) {
                isSdkException = true;
                exceptionMessage = "ErrorCode: " + ex.getErrorCode() + "\nMessage: " + ex.getMessage();
            } catch (ServerException ex) {
                exceptionMessage = "ErrorCode: " + ex.getErrorCode() + "\nMessage: " + ex.getMessage();
            } catch (Exception ex){
                exceptionMessage = "ErrorCode: " + "**********" + "\nMessage: " + ex.getMessage();
            }
            return resultData;
        }

        @Override
        protected void onCancelled() {
        }

        @Override
        protected void onPostExecute(byte[] resultData) {
            if(resultData == null){
                // エラー表示
                if(isSdkException){
                    _dlg.setTitle("SdkException 発生");

                }else{
                    _dlg.setTitle("ServerException 発生");
                }
                _dlg.setMessage(exceptionMessage + " ");
                _dlg.show();

            }
        }
    }

その他の音声合成API

#

cloud.voicetext.jp

GoogleTranlateの音声部分だけ

http://translate.google.com/translate_tts?tl=ja&q=あいうえお