当前位置:   article > 正文

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

百度tts离线语音包

声明

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

前言

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

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

在这里插入图片描述

正文

  首先我们登录这个百度智能云,然后找到语音技术。
在这里插入图片描述
点击创建应用
在这里插入图片描述
在这里插入图片描述
这里选择包名,如果你选择不需要,则只能通过网络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" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后适配api 28以上版本。

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

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

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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

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

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

当点击这个方法时会进入到离线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));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

点击这个按钮跳转到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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

下面再来看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("合成并播放 按钮已经点击");
        checkResult(result, "speak");
    }

    private void stop() {
        print("停止合成引擎 按钮已经点击");
        int result = mSpeechSynthesizer.stop();
        checkResult(result, "stop");
    }

    //  下面是UI部分

    private void initView() {
        Button mSpeak = this.findViewById(R.id.speak);
        Button mStop = this.findViewById(R.id.stop);
        mShowText = this.findViewById(R.id.showText);
        mShowText.setText(desc);
        View.OnClickListener listener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int id = v.getId();
                switch (id) {
                    case R.id.speak:
                        speak();
                        break;
                    case R.id.stop:
                        stop();
                        break;
                    default:
                        break;
                }
            }
        };
        mSpeak.setOnClickListener(listener);
        mStop.setOnClickListener(listener);
        mainHandler = new Handler() {
            /*
             * @param msg
             */
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.obj != null) {
                    print(msg.obj.toString());
                }
            }

        };
    }

    private void print(String message) {
        Log.i(TAG, message);
        mShowText.append(message + "\n");
    }

    @Override
    protected void onDestroy() {
        if (mSpeechSynthesizer != null) {
            mSpeechSynthesizer.stop();
            mSpeechSynthesizer.release();
            mSpeechSynthesizer = null;
            print("释放资源成功");
        }
        super.onDestroy();
    }

    private void checkResult(int result, String method) {
        if (result != 0) {
            print("error code :" + result + " method:" + method);
        }
    }

    //  下面是android 6.0以上的动态授权

    /**
     * android 6.0 以上需要动态申请权限
     */
    private void initPermission() {
        String[] permissions = {
                Manifest.permission.INTERNET,
                Manifest.permission.ACCESS_NETWORK_STATE,
                Manifest.permission.MODIFY_AUDIO_SETTINGS,
                Manifest.permission.WRITE_SETTINGS,
                Manifest.permission.ACCESS_WIFI_STATE,
                Manifest.permission.CHANGE_WIFI_STATE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
        };

        ArrayList<String> toApplyList = new ArrayList<>();

        for (String perm : permissions) {
            if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {
                toApplyList.add(perm);
                // 进入到这里代表没有权限.
            }
        }
        String[] tmpList = new String[toApplyList.size()];
        if (!toApplyList.isEmpty()) {
            ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);
        }

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        // 此处为android 6.0以上动态授权的回调,用户自行实现。
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346

这里的代码其实都是这个SDK中的,直接就可以使用了。我只改动了一点点。

3. 配置

然后修改AndroidManifest.xml
在这里插入图片描述

然后在activity_main.xml中增加一个按钮。

	<Button
        android:text="在线SDK合成"
        android:onClick="onlineSDK"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
  • 1
  • 2
  • 3
  • 4
  • 5

在MainActivity中增加方法。

	/**
     * 在线SDK合成
     * @param view
     */
    public void onlineSDK(View view) {
        startActivity(new Intent(this, OnlineActivity.class));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4. 运行

下面运行:

在这里插入图片描述
可以看到在线SDK合成,没有网络时是合成不了的,有网络才行,这里的声音是女声。

四、在线语音合成 - API方式

使用API方式就稍稍有一些麻烦,因为这个设计到网络的请求,而且不是一次请求,首先进行鉴权,拿到token,然后通过Token去请求合成,下载MP3文件,首先要构建网络模块,当然我也只是简单的写一下而已。

1. 鉴权返回实体

在com.llw.imagediscerndemo下新建一个model包,包下新建一个GetTokenResponse类,代码如下:

package com.llw.speechsynthesis.model;

/**
 * 获取鉴权认证Token响应实体
 *
 * @author llw
 * @date 2021/5/7 16:16
 */
public class GetTokenResponse {


    /**
     * refresh_token : 25.0141c302b0f460cd0500827fa31f22ce.315360000.1935736936.282335-24113250
     * expires_in : 2592000
     * session_key : 9mzdCS6a/7/wIFWLR8zFoYs2koSri++RGhSecVXM/vY93At4kxYRajL/xMV17MoxcTAJfadRVaSBxokIqFeQoxsZ8e3NPQ==
     * access_token : 24.2830c05696b214cf07bfbdf764599b39.2592000.1622968936.282335-24113250
     * scope : audio_voice_assistant_get brain_enhanced_asr audio_tts_post brain_speech_realtime public brain_all_scope picchain_test_picchain_api_scope brain_asr_async wise_adapt lebo_resource_base lightservice_public hetu_basic lightcms_map_poi kaidian_kaidian ApsMisTest_Test权限 vis-classify_flower lpq_开放 cop_helloScope ApsMis_fangdi_permission smartapp_snsapi_base smartapp_mapp_dev_manage iop_autocar oauth_tp_app smartapp_smart_game_openapi oauth_sessionkey smartapp_swanid_verify smartapp_opensource_openapi smartapp_opensource_recapi fake_face_detect_开放Scope vis-ocr_虚拟人物助理 idl-video_虚拟人物助理 smartapp_component smartapp_search_plugin avatar_video_test
     * session_secret : 2cdde5fd8f3fd4394c1b090e2ffa2d1c
     */

    private String refresh_token;
    private int expires_in;
    private String session_key;
    private String access_token;
    private String scope;
    private String session_secret;

    public String getRefresh_token() {
        return refresh_token;
    }

    public void setRefresh_token(String refresh_token) {
        this.refresh_token = refresh_token;
    }

    public int getExpires_in() {
        return expires_in;
    }

    public void setExpires_in(int expires_in) {
        this.expires_in = expires_in;
    }

    public String getSession_key() {
        return session_key;
    }

    public void setSession_key(String session_key) {
        this.session_key = session_key;
    }

    public String getAccess_token() {
        return access_token;
    }

    public void setAccess_token(String access_token) {
        this.access_token = access_token;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public String getSession_secret() {
        return session_secret;
    }

    public void setSession_secret(String session_secret) {
        this.session_secret = session_secret;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

下面简单的写一个网络请求框架。

2. 添加框架依赖

打开你的app的build.gradle,在dependencise{}闭包下添加如下依赖:

	//retrofit2
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'

    //权限请求框架
    implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    implementation "io.reactivex.rxjava2:rxjava:2.0.0"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

然后在android{}闭包下添加JDK1.8的支持

	compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
记得要Sync Now,这里的依赖一个是网络,一个是权限请求,后面都会用到的。

3. 搭建网络请求框架

在com.llw.speechsynthesis下新建一个network包,在这个包下新建一个NetCallBack抽象类。里面的代码如下:

package com.llw.speechsynthesis.network;

import android.util.Log;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

/**
 * 网络请求回调
 *
 * @param <T>
 */
public abstract class NetCallBack<T> implements Callback<T> {//这里实现了retrofit2.Callback

    //访问成功回调
    @Override
    public void onResponse(Call<T> call, Response<T> response) {//数据返回
        if (response != null && response.body() != null && response.isSuccessful()) {
            onSuccess(call, response);
        } else {
            onFailed(response.raw().toString());
        }
    }

    //访问失败回调
    @Override
    public void onFailure(Call<T> call, Throwable t) {
        Log.d("data str", t.toString());
        onFailed(t.toString());
    }

    //数据返回
    public abstract void onSuccess(Call<T> call, Response<T> response);

    //失败异常
    public abstract void onFailed(String errorStr);


}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

然后在network包下新增一个ServiceGenerator类,里面的代码如下:

package com.llw.speechsynthesis.network;

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * 接口地址管理
 *
 * @author llw
 */
public class ServiceGenerator {

    public static String BASE_URL = null;

    public static String getBaseUrl(int type) {
        switch (type) {
            case 0://鉴权地址
                BASE_URL = "https://openapi.baidu.com";
                break;
            case 1://合成地址
                BASE_URL = "https://tsn.baidu.com";
                break;
            default:
                break;

        }
        return BASE_URL;
    }

    /**
     * 创建服务  参数就是API服务
     *
     * @param serviceClass 服务接口
     * @param <T>          泛型规范
     * @return api接口服务
     */
    public static <T> T createService(Class<T> serviceClass, int type) {

        //创建OkHttpClient构建器对象
        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();

        //设置请求超时的时间,这里是10秒
        okHttpClientBuilder.connectTimeout(20000, TimeUnit.MILLISECONDS);

        //消息拦截器  因为有时候接口不同在排错的时候 需要先从接口的响应中做分析。利用了消息拦截器可以清楚的看到接口返回的所有内容
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();

        //setlevel用来设置日志打印的级别,共包括了四个级别:NONE,BASIC,HEADER,BODY
        //BASEIC:请求/响应行
        //HEADER:请求/响应行 + 头
        //BODY:请求/响应航 + 头 + 体
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        //为OkHttp添加消息拦截器
        okHttpClientBuilder.addInterceptor(httpLoggingInterceptor);

        //在Retrofit中设置httpclient
        //设置地址  就是上面的固定地址,如果你是本地访问的话,可以拼接上端口号  例如 +":8080"
        Retrofit retrofit = new Retrofit.Builder().baseUrl(getBaseUrl(type))
                //用Gson把服务端返回的json数据解析成实体
                .addConverterFactory(GsonConverterFactory.create())
                //放入OKHttp,之前说过retrofit是对OkHttp的进一步封装
                .client(okHttpClientBuilder.build())
                .build();
        //返回这个创建好的API服务
        return retrofit.create(serviceClass);
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

下面写接口,在network包下新增ApiService接口,代码如下:

package com.llw.speechsynthesis.network;

import com.llw.speechsynthesis.model.GetTokenResponse;


import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
import retrofit2.http.Streaming;

/**
 * API服务
 *
 * @author llw
 * @date 2021/5/8 10:48
 */
public interface ApiService {

    /**
     * 获取鉴权认证Token
     * @param grant_type 类型
     * @param client_id API Key
     * @param client_secret Secret Key
     * @return GetTokenResponse
     */
    @FormUrlEncoded
    @POST("/oauth/2.0/token")
    Call<GetTokenResponse> getToken(@Field("grant_type") String grant_type,
                                    @Field("client_id") String client_id,
                                    @Field("client_secret") String client_secret);

    /**
     * 在线API音频合成
     * @param tok 鉴权token
     * @param ctp 客户端类型选择,web端填写固定值1
     * @param cuid 用户唯一标识,用来计算UV值。建议填写能区分用户的机器 MAC 地址或 IMEI 码,长度为60字符以内
     * @param lan 固定值zh。语言选择,目前只有中英文混合模式,填写固定值zh
     * @param tex 合成的文本,使用UTF-8编码。小于2048个中文字或者英文数字,文本在百度服务器内转换为GBK后,长度必须小于4096字节(5003、5118发音人需小于512个中文字或者英文数字)
     * @return 正常合成之后返回一个音频文件
     */
    @Streaming
    @FormUrlEncoded
    @POST("/text2audio")
    Call<ResponseBody> synthesis(@Field("tok") String tok,
                                 @Field("ctp") String ctp,
                                 @Field("cuid") String cuid,
                                 @Field("lan") String lan,
                                 @Field("tex") String tex);


}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

里面有两个接口,一个是用来获取鉴权Token的,另一个是用来将文字合成音频文件的。这里会比较的麻烦一些。到此为止这个简单的网络框架就写好了。

4. 编辑布局和页面

在com.llw.speechsynthesis下新建一个OnlineAPIActivity,对应的布局是activity_online_api.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=".OnlineAPIActivity">

    <EditText
        android:layout_margin="12dp"
        android:background="#FFF"
        android:padding="12dp"
        android:gravity="top"
        android:textColor="#000"
        android:id="@+id/et_text"
        android:hint="请输入要合成的文本"
        android:layout_width="match_parent"
        android:layout_height="100dp"/>

    <Button
        android:id="@+id/btn_synth_api"
        android:text="在线API合成"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btn_play"
        android:text="播放合成的音频"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

下面先到AndroidManifest.xml中去配置Title。
在这里插入图片描述
下面回到OnlineAPIActivity看原始的代码是什么样子。

package com.llw.speechsynthesis;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

/**
 * 在线API合成
 * @author llw
 */
public class OnlineAPIActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_online_api);

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

先来完成页面的初始化。现在布局的控件有三个

声明变量

	private static final String TAG = "OnlineAPIActivity";
    
    /**
     * 输入框
     */
    private EditText etText;
    /**
     * 页面按钮 
     */
    private Button btnSynthApi, btnPlay;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

写一个初始化页面的方法

	/**
     * 初始化
     */
    private void initView() {
        etText = findViewById(R.id.et_text);
        btnSynthApi = findViewById(R.id.btn_synth_api);
        btnPlay = findViewById(R.id.btn_play);
        btnSynthApi.setOnClickListener(this);
        btnPlay.setOnClickListener(this);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里我给两个按钮添加了点击的监听,那么你需要给Activity实现控件的点击监听。
在这里插入图片描述
然后重写onClick方法

	@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_synth_api://在线API合成
                
                break;
            case R.id.btn_play://播放音频
                
                break;
            default:
                break;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

然后要在onCreate方法中调用initView()方法。
在这里插入图片描述

5. 获取鉴权Token

声明变量

	/**
     * Api服务
     */
    private ApiService service;

    /**
     * 鉴权Toeken
     */
    private String accessToken;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

然后新增一个requestApiGetToken方法,代码如下:

	/**
     * 访问API获取接口
     */
    private void requestApiGetToken() {
        String grantType = "client_credentials";
        String apiKey = "sKWlGNoBrNyaKaAycoiKFzdT";
        String apiSecret = "OwEPWPiSnMNxCF5GFZlORKzP01KwgC1Z";
        service = ServiceGenerator.createService(ApiService.class, 0);
        service.getToken(grantType, apiKey, apiSecret)
                .enqueue(new NetCallBack<GetTokenResponse>() {
                    @Override
                    public void onSuccess(Call<GetTokenResponse> call, Response<GetTokenResponse> response) {
                        if (response.body() != null) {
                            //鉴权Token
                            accessToken = response.body().getAccess_token();
                            Log.d(TAG, accessToken);
                        }
                    }

                    @Override
                    public void onFailed(String errorStr) {
                        Log.e(TAG, "获取Token失败,失败原因:" + errorStr);
                        accessToken = null;
                    }
                });
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这里的apiKey、apiSecret 的值改成自己平台创建应用时产生,你要是用我的,除了问题又问我为什么,我就只能。。。了。当然也要在onCreate中调用,这样我们已经入页面就会请求接口拿到鉴权Token。
在这里插入图片描述

下面我们运行一下,不过要先在MainActivity中写一个入口才行,在activity_main.xml中增加一个按钮。

	<Button
        android:text="在线API合成"
        android:onClick="onlineAPI"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
  • 1
  • 2
  • 3
  • 4
  • 5

然后在MainActivity中增加方法

	/**
     * 在线API合成
     * @param view
     */
    public void onlineAPI(View view) {
        startActivity(new Intent(this,OnlineAPIActivity.class));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

那么现在你就可以运行了。

在这里插入图片描述
看起来好像什么都没有做是吧。你过你看看控制台的打印。
在这里插入图片描述
这里的鉴权Token就拿到了,这种方式用户就是无感知的。其实这个鉴权Token还有优化的空间,至于怎么做,我在其他的文章写过了,你也可以自己实践。

6. 动态权限请求

因为接口请求之后会下载一个文件到手机本地,因此你需要文件读写权限、

声明变量

	/**
     * 权限请求框架
     */
    private RxPermissions rxPermissions;
  • 1
  • 2
  • 3
  • 4

然后在initView中实例化。
在这里插入图片描述
然后新怎一个方法

	/**
     * android 6.0 以上需要动态申请权限
     */
    @SuppressLint("CheckResult")
    private void requestPermission() {
        rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE)
                .subscribe(grant -> {
                    if (grant) {
                        //获得权限
                        
                    } else {
                        Toast.makeText(OnlineAPIActivity.this,"未获取到权限",Toast.LENGTH_SHORT).show();
                    }
                });
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这里也是很简单的代码,当点击在线合成API按钮时,先调用requestPermission方法进行权限的检查。
在这里插入图片描述

7. Api语音合成

这里合成是读取页面中的文本,如果输入框的内容为空则使用默认文字进行语音合成,因此需要一个默认的文本。

声明变量

	/**
     * 默认文本,当输入框未输入使用,
     */
    private String defaultText = "你好!百度。";
  • 1
  • 2
  • 3
  • 4

然后在权限通过的地方加上这样的一段代码

				//如果输入框的内容为空则使用默认文字进行语音合成
                        String text;
                        if (etText.getText().toString().trim().isEmpty()) {
                            text = defaultText;
                        } else {
                            text = etText.getText().toString().trim();
                        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述
这段代码产生了一个文本变量,需要将它传到下一个方法中,也就是合成的方法。下面来写这个方法,前面都是铺垫,让你了解这个过程,它是一步一步来的。新增方法requestSynth,代码如下:

	/**
     * 合成请求
     * @param text 需要合成语音的文本
     */
    private void requestSynth(String text) {
        service = ServiceGenerator.createService(ApiService.class, 1);
        service.synthesis(accessToken, "1", String.valueOf(System.currentTimeMillis()), "zh", text)
                .enqueue(new Callback<ResponseBody>() {
                    @Override
                    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                        if (response.isSuccessful()) {
                            Log.d(TAG,"请求成功");
                        } else {
                            Log.d(TAG, "请求失败");
                        }
                    }

                    @Override
                    public void onFailure(Call<ResponseBody> call, Throwable t) {
                        Log.e(TAG, "error");
                    }
                });
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

然后在这里调用。
在这里插入图片描述
下面可以运行了,会打印请求的结果。
在这里插入图片描述
这里点击按钮之后会请求权限,通过后会获取文本,然后进行语音合成的请求,来看看那控制台打印的结果。
在这里插入图片描述
请求成功了,那么可以进行下一步了。

8. 音频文件下载

因为这里返回的是一个音频文件,因此不能使用常规的方式来处理,下载当然是下载的项目的缓存目录里面去,当前我在Android10.0上是可以实践的,Android11.0可能要进行分区存储才行,这里说明一下。

在listener包下新增一个DownloadListener接口,里面的代码如下:

package com.llw.speechsynthesis.listener;

/**
 * 下载监听
 *
 * @author llw
 * @date 2021/5/8 9:50
 */
public interface DownloadListener {

    /**
     * 开始下载
     */
    void onStart();

    /**
     * 下载进度
     * @param progress 当前进度
     */
    void onProgress(int progress);

    /**
     * 下载完成
     * @param path 文件路径
     */ 
    void onFinish(String path);

    /**
     * 下载失败
     * @param errorInfo 错误信息
     */
    void onFail(String errorInfo);

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

然后回到OnlineAPIActivity中,声明变量

	/**
     * 文件路径
     */
    private String mPath;

    /**
     * 缓冲区大小
     */
    private static int sBufferSize = 8192;

    /**
     * 文件
     */
    private File file;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

接口的回调

	/**
     * 下载文件监听
     */
    private DownloadListener listener = new DownloadListener() {
        @Override
        public void onStart() {
            Log.d(TAG, "开始");
        }

        @Override
        public void onProgress(int progress) {
            Log.d(TAG, "进度:" + progress);
        }

        @Override
        public void onFinish(String path) {
            Log.d(TAG, "完成:" + path);
            mPath = path;
            //显示播放控件
            btnPlay.setVisibility(View.VISIBLE);

        }

        @Override
        public void onFail(String errorInfo) {
            Log.d(TAG, "异常:" + errorInfo);
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

然后新增一个写入磁盘的方法。

	/**
     * 写入磁盘
     * @param response 响应体
     * @param downloadListener 下载监听
     */
    private void writeToDisk(Response<ResponseBody> response, DownloadListener downloadListener) {
        //开始下载
        downloadListener.onStart();
        //输入流  将输入流写入文件
        InputStream is = response.body().byteStream();
        //文件总长
        long totalLength = response.body().contentLength();
        //设置文件存放路径
        file = new File(getExternalCacheDir() + "/Speech/" + "test.mp3");
        //创建文件
        if (!file.exists()) {
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdir();
            }
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
                downloadListener.onFail("createNewFile IOException");
            }
        }
        //输出流
        OutputStream os = null;
        long currentLength = 0;
        try {
            os = new BufferedOutputStream(new FileOutputStream(file));
            byte data[] = new byte[sBufferSize];
            int len;
            while ((len = is.read(data, 0, sBufferSize)) != -1) {
                os.write(data, 0, len);
                currentLength += len;
                //计算当前下载进度
                downloadListener.onProgress((int) (100 * currentLength / totalLength));
            }
            //下载完成,并返回保存的文件路径
            downloadListener.onFinish(file.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
            downloadListener.onFail("IOException");
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

然后在请求成功的分支中调用这个方法,如下图所示:
在这里插入图片描述
下面你可以运行一下:
在这里插入图片描述
合成之后,当文件下载到本地时,这个播放的按钮就会出现。下面来看看日志。

在这里插入图片描述
这样就成功了。

9. 播放

文件下载成功之后,也拿到了文件的路径了,下面就是通过这个路径去播放这个音频了。
新增一个play方法。

	 /**
     * 播放
     */
    private void play() {
        if(mPath != null){
            MediaPlayer mediaPlayer = new MediaPlayer();
            try {
                mediaPlayer.setDataSource(mPath);
                mediaPlayer.prepare();
                mediaPlayer.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在这里插入图片描述
这样就可以了。那么代码就写完了。运行一下:

在这里插入图片描述
由于是GIF,所以你听不到声音,来看这个打印的信息,一次是默认的,一次是我们自己的。
在这里插入图片描述
那么到此为止,我的所有代码就写完了。

五、源码

GitHub源码地址:SpeechSynthesisDemo

CSDN源码下载:SpeechSynthesisDemo.rar

如果本文对你有所帮助,不妨点个赞或者评论一下,也可以说说你的想法和问题,我是初学者-Study,山高水长,后会有期~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/133497
推荐阅读
相关标签
  

闽ICP备14008679号