Android 百度语音合成 (含离线在线API合成方式,详细步骤+源码)

Posted 初学者-Study

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 百度语音合成 (含离线在线API合成方式,详细步骤+源码)相关的知识,希望对你有一定的参考价值。

声明

  本文代码请使用真机运行,别用模拟器虚拟机,谢谢!

前言

  我之前写过百度的语音识别,也写过讯飞的语音识别与合成,而有读者看完后说没有百度的语音合成,想在用百度语音识别的同时使用百度的语音合成。所以就有了这篇文章,我的文章也是区别于其他人的文章,所以我有自己的风格。

感兴趣可以先扫码下载体验一下,再决定往不往下面看。

在这里插入图片描述

正文

  首先我们登录这个百度智能云,然后找到语音技术。
在这里插入图片描述
点击创建应用
在这里插入图片描述
在这里插入图片描述
这里选择包名,如果你选择不需要,则只能通过网络API来实现你的语音合成,而选择android的话就不光可以使用API还能使用SDK,不过这样的话对APK的大小会有增加。
在这里插入图片描述
这里我选择的是Android,因此需要建一个Android项目。

一、创建项目

在这里插入图片描述
先把这个com.llw.speechsynthesis包名填进去。
在这里插入图片描述
立即创建
在这里插入图片描述
查看应用详情。
在这里插入图片描述
这几个值在后面会用到的,记下来。然后回到列表中,领取免费的使用额度。

在这里插入图片描述
注意看这个提示,说明这个额度是有期限的。
在这里插入图片描述
领取之后。

二、离线语音合成

点击左侧的离线合成SDK
在这里插入图片描述
选择应用后,点击确定。
在这里插入图片描述
可以看到激活的30天内,我是5月6号激活,可能你后面看文章的时候就已经是不能用了,所以不要拿到源码之后问我为什么用不了,那只能说明你没有看文章。
在这里插入图片描述
这里看这个是单台设备授权,所以你想要增多的话就要花钱了,点击下载SDK。
在这里插入图片描述

注意这个还要激活SDK才行的。激活是需要序列号的,那么这个序列号那里来呢?点击查看详情
在这里插入图片描述
下载序列号列表,下载后打开如下
在这里插入图片描述
现在这序列号就有了,下面回到
在这里插入图片描述

下载这个SDK

在这里插入图片描述

下载后解压,下面正式来配置这个离线的语音合成了。

1. 配置AndroidManifest.xml

打开项目的AndroidManifest.xml,添加权限。

	<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

然后适配api 28以上版本。

		<!--支持api level 28 以上编译-->
        <uses-library
            android:name="org.apache.http.legacy"
            android:required="false" />

添加位置如下图。
在这里插入图片描述

2. 配置SDK

打开刚才的SDK,进入到libs文件夹下
在这里插入图片描述
将这个jar包复制到你的项目的libs下。
在这里插入图片描述
注意到它这个现在是没有展开的,说明还没有加载进去,点击工具栏右上方的小象图标进行项目资源同步。
在这里插入图片描述
同步后的你的jar就加载到项目中了,就是可以展开的。
在这里插入图片描述

进入到main文件夹下
在这里插入图片描述
复制assets和jniLibs这两个文件夹到你的项目的main下面。
在这里插入图片描述
然后展开你的assets文件夹,打开auth.properties文件。修改里面的一些内容。
在这里插入图片描述
这里面的五个值都需要进行修改,前三个值是我们在创建平台应用时生成的,我当时说了你要记下来,就是为了这里使用。那么你只要一一的对应填写替换就可以了,而applicationId:就是我们之前填写的包名,最后的sn:就是下载的序列号,有两个,任意一个都可以。那么将上面的数据改了之后如下所示:
在这里插入图片描述

3. 离线SDK初始化

离线SDK第一次初始化的时候需要联网,进行网络鉴权,鉴权成功之后就可以断网使用了,先完成这个初始化操作。修activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:text="离线SDK合成"
        android:onClick="offlineSDK"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

然后在MainActivity中写入一个方法:

	/**
     * 离线SDK合成
     * @param view
     */
    public void offlineSDK(View view) {

    }

当点击这个方法时会进入到离线SDK的页面,这个页面现在还没有的,不过我们的下载SDK里面有现成的,那就拿过来就用好了。

首先将layout下的activity_synth.xml文件复制到项目的layout下。
在这里插入图片描述
在这里插入图片描述
然后将res文件夹下的raw文件夹复制到你的项目的res下:
在这里插入图片描述

在这里插入图片描述
然后就是里面的一些配置类了。
将sample包下的选择的文件和文件夹复制到你的项目的包下。
在这里插入图片描述

在这里插入图片描述

4. 导包

然后依次打开里面的粘贴过来的类,首先是control包下的InitConfig类,里面是会有报错的,因为包名不一致。所以需要重新导包。
在这里插入图片描述
点击import左边的加号或者右边的省略号查看里面的导包信息
在这里插入图片描述
看到这里是报红的。删掉我标注的这一行错误的导包信息。然后往下滑动,到下方你点击报红的这个类,会出现一个提示如下图所示:可以通过快捷键Alt + Enter快速导包
在这里插入图片描述
导包之后这个类就不报错了,就能正常使用了
在这里插入图片描述
那么你刚才复制过来的类都需要重新打开一次,看看里面的包是否有异常,有的话就按刚才的方法来解决就好了。当你把所有的类检查一遍之后,确保都没有异常之后,就可以开始进行这个初始化了。

修改MainActivity中的代码

	/**
     * 离线SDK合成
     * @param view
     */
    public void offlineSDK(View view) {
        startActivity(new Intent(this,SynthActivity.class));
    }

点击这个按钮跳转到SynthActivity中。别忘了要在AndroidManifest.xml中注册这个Activity。
在这里插入图片描述

5. 运行

下面运行一下:

在这里插入图片描述
是有声音的,不过这是GIF图,所以你只能看到我的演示效果。那么到此为止,这个离线合成就弄完了,具体的细节你要多看这个SDK的代码,我个人觉得代码太多了,有些乱。

三、在线语音合成 - SDK方式

1. 创建页面

在线合成的方式其实和离线差不了多少,在com.llw.speechsynthesis包下新建一个OnlineActivity,布局是activity_online.xml,布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="50dp"
        android:orientation="horizontal"
        android:weightSum="3">

        <Button
            android:id="@+id/speak"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:lines="2"
            android:text="合成并播放"
            android:textSize="12dp" />

        <Button
            android:id="@+id/stop"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_weight="2"
            android:lines="2"
            android:text="停止合成引擎"
            android:textSize="12dp" />

    </LinearLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/btn"
        >

        <TextView
            android:id="@+id/showText"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="10dp"
            android:background="@android:color/darker_gray"
            android:minLines="3"
            android:scrollbars="vertical" />
    </ScrollView>

</LinearLayout>

下面再来看OnlineActivity的代码

2. 编辑代码

package com.llw.speechsynthesis;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.baidu.tts.chainofresponsibility.logger.LoggerProxy;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.client.TtsMode;
import com.llw.speechsynthesis.control.InitConfig;
import com.llw.speechsynthesis.listener.UiMessageListener;
import com.llw.speechsynthesis.util.Auth;
import com.llw.speechsynthesis.util.AutoCheck;
import com.llw.speechsynthesis.util.FileUtil;
import com.llw.speechsynthesis.util.IOfflineResourceConst;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * 除了SDK,该类没有任何依赖,可以直接复制进你的项目
 * <p>
 * 默认TEMP_DIR = "/sdcard/baiduTTS"; // 重要!请手动将assets目录下的3个dat 文件复制到该目录
 * 确保 TEXT_FILENAME 和 MODEL_FILENAME 存在
 * Created by fujiayi on 2017/9/14.
 */

public class OnlineActivity extends AppCompatActivity implements IOfflineResourceConst {

    /**
     * 要合成的文本,可以自行改动。
     */
    private static final String TEXT = "欢迎使用百度语音合成,请在代码中修改合成文本";

    protected String appId;

    protected String appKey;

    protected String secretKey;

    protected String sn; // 纯离线合成SDK授权码;离在线合成SDK没有此参数

    //TtsMode.ONLINE 纯在线
    private TtsMode ttsMode = TtsMode.ONLINE;

    private boolean isOnlineSDK = TtsMode.ONLINE.equals(DEFAULT_SDK_TTS_MODE);
    // ================ 纯离线sdk或者选择TtsMode.ONLINE  以下参数无用;
    private static final String TEMP_DIR = "/sdcard/baiduTTS"; // 重要!请手动将assets目录下的3个dat 文件复制到该目录

    // 请确保该PATH下有这个文件
    private static final String TEXT_FILENAME = TEMP_DIR + "/" + TEXT_MODEL;

    // 请确保该PATH下有这个文件 ,m15是离线男声
    private static final String MODEL_FILENAME = TEMP_DIR + "/" + VOICE_MALE_MODEL;

    // ===============初始化参数设置完毕,更多合成参数请至getParams()方法中设置 =================

    protected SpeechSynthesizer mSpeechSynthesizer;

    // =========== 以下为UI部分 ==================================================

    private TextView mShowText;

    protected Handler mainHandler;

    private String desc; // 说明文件


    private static final String TAG = "MiniActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        appId = Auth.getInstance(this).getAppId();
        appKey = Auth.getInstance(this).getAppKey();
        secretKey = Auth.getInstance(this).getSecretKey();
        sn = Auth.getInstance(this).getSn(); // 纯离线合成必须有此参数;离在线合成SDK没有此参数
        desc = FileUtil.getResourceText(this, R.raw.mini_activity_description);
        setContentView(R.layout.activity_online);
        initView();
        initPermission();
        initTTs();
    }

    /**
     * 注意此处为了说明流程,故意在UI线程中调用。
     * 实际集成中,该方法一定在新线程中调用,并且该线程不能结束。具体可以参考NonBlockSyntherizer的写法
     */
    private void initTTs() {
        LoggerProxy.printable(true); // 日志打印在logcat中
        boolean isSuccess;
        if (!isOnlineSDK) {
            // 检查2个离线资源是否可读
            isSuccess = checkOfflineResources();
            if (!isSuccess) {
                return;
            } else {
                print("离线资源存在并且可读, 目录:" + TEMP_DIR);
            }
        }
        // 日志更新在UI中,可以换成MessageListener,在logcat中查看日志
        SpeechSynthesizerListener listener = new UiMessageListener(mainHandler);

        // 1. 获取实例
        mSpeechSynthesizer = SpeechSynthesizer.getInstance();
        mSpeechSynthesizer.setContext(this);

        // 2. 设置listener
        mSpeechSynthesizer.setSpeechSynthesizerListener(listener);

        // 3. 设置appId,appKey.secretKey
        int result = mSpeechSynthesizer.setAppId(appId);
        checkResult(result, "setAppId");
        result = mSpeechSynthesizer.setApiKey(appKey, secretKey);
        checkResult(result, "setApiKey");

        // 4. 如果是纯离线SDK需要离线功能的话
        if (!isOnlineSDK) {
            // 文本模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在并且可读
            mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, TEXT_FILENAME);
            // 声学模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在并且可读
            mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, MODEL_FILENAME);

            mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);
            // 该参数设置为TtsMode.MIX生效。
            // MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
            // MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
            // MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
            // MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线



        }

        // 5. 以下setParam 参数选填。不填写则默认值生效
        // 设置在线发声音人: 0 普通女声(默认) 1 普通男声  3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");
        // 设置合成的音量,0-15 ,默认 5
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "9");
        // 设置合成的语速,0-15 ,默认 5
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, "5");
        // 设置合成的语调,0-15 ,默认 5
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, "5");

        // mSpeechSynthesizer.setAudiostreamType(AudioManager.MODE_IN_CALL); // 调整音频输出

        if (sn != null) {
            // 纯离线sdk这个参数必填;离在线sdk没有此参数
            mSpeechSynthesizer.setParam(PARAM_SN_NAME, sn);
        }

        // x. 额外 : 自动so文件是否复制正确及上面设置的参数
        Map<String, String> params = new HashMap<>();
        // 复制下上面的 mSpeechSynthesizer.setParam参数
        // 上线时请删除AutoCheck的调用
        if (!isOnlineSDK) {
            params.put(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, TEXT_FILENAME);
            params.put(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, MODEL_FILENAME);
        }

        // 检测参数,通过一次后可以去除,出问题再打开debug
        InitConfig initConfig = new InitConfig(appId, appKey, secretKey, ttsMode, params, listener);
        AutoCheck.getInstance(getApplicationContext()).check(initConfig, new Handler() {
            @Override
            /**
             * 开新线程检查,成功后回调
             */
            public void handleMessage(Message msg) {
                if (msg.what == 100) {
                    AutoCheck autoCheck = (AutoCheck) msg.obj;
                    synchronized (autoCheck) {
                        String message = autoCheck.obtainDebugMessage();
                        print(message); // 可以用下面一行替代,在logcat中查看代码
                        // Log.w("AutoCheckMessage", message);
                    }
                }
            }

        });

        // 6. 初始化
        result = mSpeechSynthesizer.initTts(ttsMode);
        checkResult(result, "initTts");

    }

    /**
     * 在线SDK不需要调用,纯离线SDK会检查资源文件
     *
     * 检查 TEXT_FILENAME, MODEL_FILENAME 这2个文件是否存在,不存在请自行从assets目录里手动复制
     *
     * @return 检测是否成功
     */
    private boolean checkOfflineResources() {
        String[] filenames = {TEXT_FILENAME, MODEL_FILENAME};
        for (String path : filenames) {
            File f = new File(path);
            if (!f.canRead()) {
                print("[ERROR] 文件不存在或者不可读取,请从demo的assets目录复制同名文件到:"
                        + f.getAbsolutePath());
                print("[ERROR] 初始化失败!!!");
                return false;
            }
        }
        return true;
    }

    private void speak() {
        /* 以下参数每次合成时都可以修改
         *  mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");
         *  设置在线发声音人: 0 普通女声(默认) 1 普通男声  3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
         *  mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "5"); 设置合成的音量,0-15 ,默认 5
         *  mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, "5"); 设置合成的语速,0-15 ,默认 5
         *  mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, "5"); 设置合成的语调,0-15 ,默认 5
         *
         */

        if (mSpeechSynthesizer == null) {
            print("[ERROR], 初始化失败");
            return;
        }
        int result = mSpeechSynthesizer.speak(TEXT);
        mShowText.setText("");
        print(以上是关于Android 百度语音合成 (含离线在线API合成方式,详细步骤+源码)的主要内容,如果未能解决你的问题,请参考以下文章

集成Android免费语音合成功能(在线离线离在线融合)

语音合成 API 离线语言

人工智能交互集成在线语音合成能力的Tips

人工智能交互集成在线语音合成能力的Tips

python实现语音在线合成,让你的小说自己念给你听

基于C# 百度AI和科大汛飞语音合成SDK