为 Android 应用添加语音翻译

本教程介绍如何为 Android 应用提供语音翻译功能。本教程中的示例使用微服务接收语音消息,将此消息翻译为一组预定义语言,并将翻译好的消息存储在音频文件中。Android 客户端应用会根据用户请求下载并播放翻译好的音频文件。

解决方案概览

该解决方案包括以下组件:

微服务

微服务在 Cloud Functions for Firebase 上实现,并使用以下 Cloud AI 产品来翻译消息:

微服务将翻译好的语音消息存储在 Cloud Storage for Firebase 的存储分区中。

客户端应用

客户端组件是一个 Android 应用,可以录制语音消息并从 Cloud Storage 存储分区下载翻译好的消息。该示例是使用 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. 确保您的 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 控制台的左侧菜单中,选择开发组中的身份验证
  2. 记下要用于测试应用的用户帐号的用户 UID 值。用户 UID 是一个长度为 28 个字符的字符串

如需对该用户帐号启用读取权限,您必须创建存储安全规则。如需创建安全规则,请执行以下操作:

  1. Firebase 控制台的左侧菜单中,选择开发组中的存储
  2. 记下默认存储分区网址,该网址格式为 gs://[FIREBASE_PROJECT_ID].appspot.com 并显示在链接图标旁边。您需要此值来部署微服务。
  3. 存储页面中,转到规则部分,在 service firebase.storage 部分中添加以下规则:

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

    ACCOUNT_USER_UID 替换为在之前步骤中获得的用户 UID 值。

如需了解详情,请参阅 Firebase 文档中的存储安全规则入门

构建和部署微服务

如需构建微服务,请打开终端窗口,然后转到您在上一部分中克隆的 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 示例应用需要使用微服务网址来启用语音翻译功能。如需检索微服务网址,请执行以下操作:

  1. Firebase 控制台的左侧菜单中,选择开发组中的函数
  2. 微服务网址显示在触发器列中,格式为 https://[REGION_ID]-[FIREBASE_PROJECT_ID].cloudfunctions.net/[FUNCTION_NAME]

如需配置应用以使用微服务,请打开 firebase-android-client 代码库中的 app/src/main/res/values/speech_translation.xml 文件,并使用微服务网址更新 speechToSpeechEndpoint 字段。

运行 Android 应用

如需在应用中使用语音翻译功能,您必须使用支持使用内置麦克风录制音频的设备,例如硬件设备。

如需在应用中使用语音翻译功能,请执行以下操作:

  1. 确保硬件设备使用构建和部署微服务部分中配置的其中一种语言。如需更改语言,请打开设备上的设置应用,然后转到系统 > 语言和输入 > 语言
  2. 在 Android Studio 中打开 Playchat 项目,然后使用 USB 线将硬件设备连接到计算机。如需了解详情,请参阅设置设备以进行开发
  3. 点击 Android Studio 中的运行以在设备上构建和运行应用。
  4. 在 Playchat 应用中,点按麦克风图标开始录制简短消息,然后再点按麦克风图标以停止录制。
  5. 几秒钟后,Playchat 应用会在屏幕上显示录制的消息的文本。点按该消息以播放音频版本。
  6. 将设备配置为使用另一种受支持的语言。
  7. Playchat 应用会以该语言显示之前录制的消息。点按该消息可播放该语言的音频版本。

下面的屏幕截图是 Playchat 应用显示已翻译为法语的消息:

Android 中的语音翻译功能

查看代码

客户端应用执行以下任务以支持语音翻译功能:

  1. 使用 Speech-to-Text API 最佳做法中描述的推荐参数录制音频。
  2. 使用 Base64 方案对音频进行编码,以可嵌入 HTTP 请求的字符串格式表示音频。
  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_SOURCEMediaRecorder.AudioSource.UNPROCESSED 表示未处理的音频源,因为应用降噪或增益控制等信号处理算法会降低识别准确率。
  • SAMPLE_RATE_IN_HZ:样本使用 16,000 值作为音频源的原生采样率。
  • CHANNEL_CONFIGAudioFormat.CHANNEL_IN_MONO 表示录音中只有一个声道。该样本在录音中假定只使用某一个人的声音。
  • AUDIO_FORMATAudioFormat.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 存储分区中检索音频文件,应用会使用下载网址,其中包含可以从 Firebase 控制台撤消的令牌(如果需要)。您可以获取调用 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. 如需删除应用版本,请点击删除