Android アプリに音声翻訳を追加する

このチュートリアルでは、Android アプリに音声翻訳機能を提供する方法を示します。このチュートリアルのサンプルで使用するマイクロサービスは、音声メッセージを受信して、それを一連の事前定義された言語に翻訳し、翻訳後のメッセージを音声ファイルに保存します。クライアント Android アプリは、この翻訳後の音声ファイルをユーザー リクエストに応じてダウンロードして再生します。

ソリューションの概要

このソリューションは、次のコンポーネントで構成されています。

マイクロサービス

マイクロサービスは Cloud Functions for Firebase 上に実装され、次の Cloud AI プロダクトを使用してメッセージを翻訳します。

このマイクロサービスは翻訳後の音声メッセージを Cloud Storage for Firebase 内のバケットに格納します。

クライアント アプリ

クライアント コンポーネントは、音声メッセージを録音し、翻訳後のメッセージを Cloud Storage バケットからダウンロードする Android アプリです。サンプルアプリとして使用するのは、Firebase と App Engine フレキシブル環境を使用して Android アプリをビルドのチュートリアルで使用したチャットアプリです。このチュートリアルでは、このサンプルアプリを拡張して音声翻訳機能を実装する方法を説明します。

次の図は、マイクロサービスとクライアント アプリ間のやり取りを示しています。

ソリューション アーキテクチャの概要

マイクロサービスが行うタスクは次のとおりです。

  1. Base64 エンコード形式の音声メッセージを受信します。
  2. Speech-to-Text API を使用して、音声メッセージをテキストに変換します。
  3. Translation API を使用して、テキストに変換されたメッセージを翻訳します。
  4. Text-to-Speech API を使用して、翻訳後のメッセージを合成します。
  5. 翻訳後の音声メッセージを Cloud Storage バケットに格納します。
  6. クライアントにレスポンスを送信します。レスポンスには、翻訳後の音声メッセージのロケールが組み込まれます。

マイクロサービス アーキテクチャ

クライアント アプリが行うタスクは次のとおりです。

  1. 音声メッセージを録音します。最終的な精度を上げるため、録音の際には Speech-to-Text API のベスト プラクティスに従うようにします。アプリはデバイス上に搭載されたマイクを使用して音声をキャプチャします。
  2. 音声メッセージを Base64 形式にエンコードします。
  3. エンコードした音声メッセージを本文に含めた HTTP リクエストをマイクロサービスに送信します。
  4. マイクロサービスから HTTP レスポンスを受信します。このレスポンスには、翻訳後の音声メッセージのロケールが組み込まれています。
  5. Cloud Storage バケットにリクエストを送信して、翻訳後の音声メッセージが保存されたファイルを取得します。
  6. 翻訳された音声メッセージを再生します。

目標

このチュートリアルでは、以下の手順を紹介します。

  • Cloud Functions for Firebase を使用して、次の Cloud AI プロダクトを使用して音声メッセージを翻訳するために必要なロジックをカプセル化したマイクロサービスを構築します。
    • Speech-to-Text API
    • Translation API
    • Text-to-Speech API
  • 音声データを Speech-to-Text API へ渡す際に推奨される方法に従い、Android Framework API を使って音声を録音します。
  • Cronet ライブラリを使用して、クライアント アプリからマイクロサービスに音声データをアップロードし、Cloud Storage から翻訳されたメッセージをダウンロードします。Cronet ライブラリについて詳しくは、Android デベロッパー向けドキュメントの Cronet を使用したネットワーク操作の実行をご覧ください。

費用

このチュートリアルでは、Firebase と App Engine フレキシブル環境を使用して Android アプリをビルドで実装したサンプルアプリを拡張します。上述したチュートリアル内の料金セクションを確認し、次の追加料金もあわせて考慮してください。

  • Firebase では、Cloud Functions の使用量について割り当てを定義しています。この割り当てにより、リソース、時間、レートに関する上限が指定されています。詳しくは、Firebase ドキュメントの割り当てと上限をご覧ください。
  • Speech-to-Text API の使用料金は、正常に処理された音声の長さに基づいて月単位で請求されます。所定の処理時間は毎月無料で使用できます。詳しくは、Speech-to-Text API の料金をご覧ください。
  • Translation API の使用料金は、API に処理対象として送信された文字の量に基づいて月単位で請求されます。詳しくは、Translation API の料金をご覧ください。
  • Text-to-Speech API の使用料金は、音声に合成された文字の量に基づいて月単位で請求されます。所定の文字数は毎月無料で使用できます。詳しくは、Text-to-Speech API の料金をご覧ください。
  • Firebase Storage の使用料は、Google Cloud Storage の料金として処理されます。詳しくは、Cloud Storage の料金をご覧ください。

始める前に

Firebase と App Engine フレキシブル環境を使用して Android アプリをビルドのチュートリアルに従って、次のソフトウェアをインストールします。

音声翻訳機能をテストするために、Android 7.0(API レベル 24)以降で稼働するハードウェア デバイスを入手してください。

サンプルコードのクローンの作成

次のコマンドを使用して、nodejs-docs-samples リポジトリのクローンを作成します。このリポジトリに、マイクロサービス コードが含まれています。

git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git
    

Google Cloud プロジェクトの課金と API を有効化する

このチュートリアルで使用する「Firebase と App Engine フレキシブル環境を使用して Android アプリをビルド」で作成した Playchat プロジェクトには、App Engine Admin API と Compute Engine API が必要です。

音声翻訳リクエストを処理するには、マイクロサービスには次の API が必要です。

  • Text-to-Speech API
  • Cloud Translation API
  • Speech-to-Text API

必要な API を有効にするには:

  1. Google Cloud Console で、Playchat プロジェクトを選択します。

    プロジェクト ページに移動

  2. Google Cloud プロジェクトに対して課金が有効になっていることを確認します。 プロジェクトに対して課金が有効になっていることを確認する方法を学習する

  3. App Engine, Speech-to-Text, Translation, and Text-to-Speech API を有効にします。

    API を有効にする

Cloud Storage for Firebase 上でデフォルト バケットを構成する

マイクロサービスは、Firebase プロジェクト内のデフォルトの Cloud Storage バケットに翻訳後の音声ファイルを格納します。音声ファイルを取得するために使用するユーザー アカウントに対して、読み取りアクセス権を有効にする必要があります。

読み取りアクセス権を有効にするには、アカウントの Firebase ユーザー UID が必要です。ユーザー UID を取得するには、次の手順に従います。

  1. Firebase コンソールの左側のメニューで、[開発] グループの [Authentication] を選択します。
  2. アプリをテストするために使うユーザー アカウントの [ユーザー UID] の値をメモします。ユーザー UID は 28 文字の文字列です。

ユーザー アカウントに対して読み取りアクセス権を有効にするには、ストレージ セキュリティ ルールを作成する必要があります。セキュリティ ルールを作成するには、次の手順に従います。

  1. Firebase コンソールの左側のメニューで、[開発] グループの [Storage] を選択します。
  2. デフォルト バケットの URL をメモします。この URL は、gs://[FIREBASE_PROJECT_ID].appspot.com の形式でリンクアイコンの横に表示されています。マイクロサービスをデプロイするには、この値が必要です。
  3. [Storage] ページの [Rules] セクションに移動し、service firebase.storage セクション内に次のルールを追加します。

     match /b/{bucket}/o {
           match /{allPaths=**} {
             allow read: if request.auth.uid == "[ACCOUNT_USER_UID]";
           }
         }
        

    ACCOUNT_USER_UID は、前の手順で取得したユーザー UID の値で置き換えます。

詳しくは、Firebase ドキュメントの Storage セキュリティ ルールを使ってみるをご覧ください。

マイクロサービスを構築してデプロイする

マイクロサービスを構築するには、ターミナル ウィンドウを開き、前のセクションでクローンを作成した nodejs-docs-samples リポジトリ内の functions/speech-to-speech/functions フォルダに移動します。

マイクロサービス コードに含まれる .nvmrc ファイルで、Node.js のバージョンが宣言されています。アプリを実行するには、このバージョンを使用する必要があります。次のコマンドを実行して、NVM をセットアップし、マイクロサービスの依存関係をインストールします。

nvm install && nvm use && npm install
    

コマンドライン インターフェースを使用して Firebase にログインするには、次のコマンドを実行します。

firebase login
    

マイクロサービスには、次の環境変数が必要です。

  • OUTPUT_BUCKET: Firebase プロジェクト内のデフォルトの Cloud Storage バケット。
  • SUPPORTED_LANGUAGE_CODES: マイクロサービスでサポートしている言語コードのカンマ区切りのリスト。

コマンドライン インターフェースで、次のコマンドを使用して必要な環境データを宣言します。FIREBASE_PROJECT_ID プレースホルダは、前のセクションで調べた値で置き換えます。

firebase functions:config:set playchat.output_bucket="gs://[FIREBASE_PROJECT_ID].appspot.com"
    firebase functions:config:set playchat.supported_language_codes="en,es,fr"
    

Android アプリを構成する

Playchat サンプルアプリで音声翻訳機能を有効にするには、マイクロサービスの URL が必要です。マイクロサービスの URL を取得するには、次の手順に従います。

  1. Firebase コンソールの左側のメニューで、[開発] グループの [Functions] を選択します。
  2. マイクロサービスの URL が [トリガー] 列に表示されます。この URL は https://[REGION_ID]-[FIREBASE_PROJECT_ID].cloudfunctions.net/[FUNCTION_NAME] の形式になっています。

マイクロサービスと連携するようにアプリを構成するには、firebase-android-client リポジトリに含まれている app/src/main/res/values/speech_translation.xml ファイルを開き、speechToSpeechEndpoint フィールドをマイクロサービスの URL に更新します。

Android アプリを実行する

アプリで音声翻訳機能を使用するには、内蔵マイクで音声録音をサポートしているデバイス(ハードウェア デバイスなど)を使用する必要があります。

アプリで音声翻訳機能を使用するには:

  1. ハードウェア デバイスで使用される言語が、マイクロサービスを構築してデプロイするのセクションで構成した言語のいずれかであることを確認します。言語を変更する場合は、デバイス上の [設定] アプリを開き、[システム] > [言語と入力] > [言語] に移動します。
  2. Android Studio で Playchat プロジェクトを開き、USB ケーブルを使用してハードウェア デバイスをパソコンに接続します。詳しくは、開発用デバイスのセットアップをご覧ください。
  3. Android Studio で [Run] をクリックしてアプリをビルドし、デバイス上で実行します。
  4. Playchat アプリでマイクアイコンをタップして録音を開始し、短いメッセージを録音した後、マイクアイコンを再度タップして録音を停止します。
  5. 数秒後、Playchat アプリの画面上に録音したメッセージのテキストが表示されます。メッセージをタップして音声バージョンのメッセージを再生します。
  6. サポートされている別の言語を使用するようにデバイスを構成します。
  7. Playchat アプリの画面上に、以前録音したメッセージが、新たに別のサポート対象の言語で表示されます。メッセージをタップすると、新しい言語で音声バージョンのメッセージを再生します。

次の Playchat アプリのスクリーンショットには、フランス語に翻訳されたメッセージが表示されています。

Android での音声翻訳機能

コードの確認

クライアント アプリは音声翻訳機能をサポートするために、次のタスクを実行します。

  1. Speech-to-Text API のベスト プラクティスで説明されている推奨パラメータを使用して音声を録音します。
  2. HTTP リクエストに埋め込める文字列形式で音声を表すために、Base64 方式で音声をエンコードします。
  3. HTTP リクエストをマイクロサービスに送信します。リクエストには、エンコードされた音声メッセージに加え、ペイロードに関する追加の情報を提供するメタデータが組み込まれます。アプリは Cronet ライブラリを使用してネットワーク リクエストを管理します。
  4. ユーザーが翻訳されたメッセージを再生しようとすると、アプリは対応する音声ファイルをダウンロードするために、その翻訳されたメッセージが格納されている Cloud Storage バケットに対し、認証済み HTTP リクエストを発行します。

次のコード例に、サンプルアプリが録音構成パラメータを指定するために使用する定数が示されています。

private static final int AUDIO_SOURCE = MediaRecorder.AudioSource.UNPROCESSED;
    private static final int SAMPLE_RATE_IN_HZ = 16000;
    private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
    private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
  • AUDIO_SOURCE: MediaRecorder.AudioSource.UNPROCESSED は、未処理の音源を指定しています。これは、ノイズ低減やゲイン コントロールなどの信号処理アルゴリズムを適用すると、認識の精度が低下するためです。
  • SAMPLE_RATE_IN_HZ: サンプルでは、音源のネイティブ サンプルレートとして値 16,000 を使用します。
  • CHANNEL_CONFIG: AudioFormat.CHANNEL_IN_MONO は、録音に含まれる音声チャネルが 1 つのみであることを指定しています。サンプルでは、1 人の声だけが録音されることを前提としています。
  • AUDIO_FORMAT: AudioFormat.ENCODING_PCM_16BIT は、サンプルあたり 16 ビットを使用したリニア PCM 音声データ形式を指定しています。リニア PCM はロスレス形式であり、音声認識に適しています。

クライアント アプリは AudioRecord API を使用して、内蔵マイクからオーディオを録音し、.WAV ファイルをデバイスに保存します。詳しくは、Playchat サンプルの RecordingHelper クラスをご覧ください。

Base64 方式で音声をエンコードするために、サンプルでは Android Framework の Base64 クラスを使用しています。エンコードされた音声に改行を含めることはできません。改行は NO_WRAP フラグを使用することによって省略されます。次の例は、Base64 クラスを使って音声をエンコードする方法を示しています。

public static String encode(File inputFile) throws IOException {
        byte[] data = new byte[(int) inputFile.length()];
        DataInputStream input = new DataInputStream(new FileInputStream(inputFile));
        int readBytes = input.read(data);
        Log.i(TAG, readBytes + " read from input file.");
        input.close();
        return Base64.encodeToString(data, Base64.NO_WRAP);
    }

エンコードされた音声をマイクロサービスに送信するために、クライアント アプリは次のパラメータを指定した HTTP リクエストを発行します。

  • メソッド: POST
  • コンテンツ タイプ: application/json
  • 本文: 次の属性を持つ JSON オブジェクト:
    • encoding: LINEAR16 文字列
    • sampleRateHertz: 録音された音声のサンプルレート。例: 16000
    • languageCode: 録音されたメッセージの言語コード。クライアント アプリは、デバイスの設定で構成されている言語でメッセージが録音されていることを前提としています。例: en-US
    • audioContent: Base64 方式でエンコードされた音声メッセージ

次の例は、リクエスト本文に必要な属性を含めた JSON オブジェクトを作成する方法を示しています。

JSONObject requestBody = new JSONObject();
    try {
        requestBody.put("encoding", SPEECH_TRANSLATE_ENCODING);
        requestBody.put("sampleRateHertz", sampleRateInHertz);
        requestBody.put("languageCode", context.getResources().getConfiguration().getLocales().get(0));
        requestBody.put("audioContent", base64EncodedAudioMessage);
    } catch(JSONException e) {
        Log.e(TAG, e.getLocalizedMessage());
        translationListener.onTranslationFailed(e);
    }

HTTP リクエストを作成する方法について詳しくは、Playchat サンプルアプリの SpeechTranslationHelper クラスをご覧ください。

Cloud Storage バケットから音声ファイルを取得するために、アプリはトークンを組み込んだダウンロード URL を使用します。このトークンは、必要に応じて Firebase コンソールで取り消すことができます。次の例に示されているように、ダウンロード URL を取得するには getDownloadUrl() メソッドを呼び出します。

FirebaseStorage storage = FirebaseStorage.getInstance();
    StorageReference gsReference = storage.getReferenceFromUrl(gcsUrl);
    gsReference.getDownloadUrl().addOnCompleteListener(getDownloadUriListener);

マイクロサービスは、音声翻訳機能をサポートするために次のタスクを行います。

  1. Base64 エンコードされた音声を含む音声翻訳リクエストを受信します。
  2. エンコードされた音声を Speech-to-Text API に送信し、音声から文字に変換されたソース言語のテキストを受信します。
  3. サポートされている各言語について、変換されたテキストを Translation API に送信し、翻訳されたテキストを受信します。
  4. サポートされている各言語について、翻訳テキストを Text-to-Speech API に送信し、翻訳された音声を受信します。
  5. 翻訳後の音声ファイルを Cloud Storage バケットにアップロードします。

次のサンプルコードに示されているように、マイクロサービスは Cloud API 呼び出しによる出力を、次の API 呼び出しの入力として使用します。

const [sttResponse] = await callSpeechToText(
      inputAudioContent,
      inputEncoding,
      inputSampleRateHertz,
      inputLanguageCode
    );

    // The data object contains one or more recognition
    // alternatives ordered by accuracy.
    const transcription = sttResponse.results
      .map((result) => result.alternatives[0].transcript)
      .join('\n');
    responseBody.transcription = transcription;
    responseBody.gcsBucket = outputBucket;

    const translations = [];
    supportedLanguageCodes.forEach(async (languageCode) => {
      const translation = {languageCode: languageCode};
      const outputFilename =
        request.body.outputFilename ||
        `${uuid.v4()}.${outputAudioEncoding.toLowerCase()}`;

      try {
        const [textTranslation] = await callTextTranslation(
          languageCode,
          transcription
        );
        translation.text = textTranslation;

        const [{audioContent}] = await callTextToSpeech(
          languageCode,
          textTranslation
        );
        const path = `${languageCode}/${outputFilename}`;

        console.log('zzx', audioContent);

        await uploadToCloudStorage(path, audioContent);

        console.log(`Successfully translated input to ${languageCode}.`);
        translation.gcsPath = path;
        translations.push(translation);
        if (translations.length === supportedLanguageCodes.length) {
          responseBody.translations = translations;
          console.log(`Response: ${JSON.stringify(responseBody)}`);
          response.status(200).send(responseBody);
        }
      } catch (error) {
        console.error(
          `Partial error in translation to ${languageCode}: ${error}`
        );
        translation.error = error.message;
        translations.push(translation);
        if (translations.length === supportedLanguageCodes.length) {
          responseBody.translations = translations;
          console.log(`Response: ${JSON.stringify(responseBody)}`);
          response.status(200).send(responseBody);
        }
      }
    });

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにする手順は次のとおりです。

Google Cloud および Firebase プロジェクトを削除します。

課金を発生させないようにする最も簡単な方法は、このチュートリアルで作成したプロジェクトを削除することです。Firebase コンソールでプロジェクトを作成した場合でも、Google Cloud Console で削除できます。これは、Firebase プロジェクトと Google Cloud プロジェクトは同一であるためです。

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] ページに移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

App Engine アプリのデフォルト以外のバージョンを削除する

Google Cloud プロジェクトと Firebase プロジェクトを削除しない場合は、App Engine フレキシブル環境アプリのデフォルト以外のバージョンを削除すればコストを削減できます。

  1. Cloud Console で、App Engine の [バージョン] ページに移動します。

    [バージョン] ページに移動

  2. デフォルト以外で削除するアプリのバージョンのチェックボックスを選択します。
  3. [削除] をクリックして、アプリのバージョンを削除します。