百度语音SDK提供:

  • 语音识别:将声音转成文字

  • 语音合成:将文字转成语音文件,然后播放语音文件,即文字变声音。

  • 语音唤醒:语音唤醒,激活运用程序

在这里,本篇介绍百度语音合成的使用。

百度语音介绍:

  1. 永久免费
  2. 多语言(中文,中英混读)多音色(男音,女音)可选
  3. 离线与在线,节省流量

详情参考,百度语音介绍

百度语音使用流程指南:

  1. 成为开发者,创建运用,选择服务
  2. 下载对应的SDK,集成开发。

详情参考,百度语音接入指南百度语音SDK下载.

项目集成百度语音SDK:

  1. 在androidStudio中按照其AS特有的使用方式添加添加so库,相关的jar。

    这里写图片描述

    添加完so库,jar库后,需Gradle中配置如下:

    android {
    ........
     sourceSets {
        main {
            //设置so库依赖路径
            jniLibs.srcDirs = ['libs']
        }
    }
    }
    dependencies {
     ..... 
     compile files('libs/com.baidu.tts_2.3.0.jar')
    }
  2. 在androidStudio中assert文件夹下添加文本模型文件.dat,和声音模型文件.dat(无网络,离线下使用的)。这里因项目需求,只添加下载到女生模型文件。

    这里写图片描述

  3. 添加以下权限:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
  4. Proguard配置(不需要混淆sdk中jar包)

  5. 编写相关使用代码如下:

    5.1 加载离线资源,先创建存储的文件夹,然后将assert中模型文件一个个通过Stream读写(异步操作,可考虑工作线程或者RxJava完成)指定的文件夹下:

    /**
     * 百度tts文件夹
     */
    public static final String BAIDU_TTS_DIR_NAME = "baiduTts";
    public static final String SPEECH_FEMALE_MODE_NAME = "bd_etts_ch_speech_female.dat";
    public static final String TEXT_MODEL_NAME = "bd_etts_ch_text.dat";
    
    /**
     * 初始化语音文件的配置
     *
     * @param context
     */
    private void initFileConfig(Context context) {
        File dirFile = getDirFile(context);
        if (dirFile != null && !dirFile.exists()) {
            dirFile.mkdir();
            copyFromAssertsToSDCard(context, SPEECH_FEMALE_MODE_NAME, getFilePath(dirFile, SPEECH_FEMALE_MODE_NAME));
            copyFromAssertsToSDCard(context, TEXT_MODEL_NAME, getFilePath(dirFile, TEXT_MODEL_NAME));
        }
    }
    
    /**
     * 获取目录
     *
     * @param context
     * @return
     */
    public File getDirFile(Context context) {
        return MyUtils.getCacheFile(context, BAIDU_TTS_DIR_NAME);
    }
    
    /**
     * 获取文件路径
     *
     * @param dirFile
     * @param fileName
     * @return
     */
    public String getFilePath(File dirFile, String fileName) {
        return dirFile.getAbsolutePath() + File.separator + fileName;
    }
    
    /**
     * 将文件写入sdcard中
     *
     * @param context
     * @param assertFileName
     * @param filePath
     */
    private void copyFromAssertsToSDCard(Context context, String assertFileName, String filePath) {
        FileOutputStream outputStream = null;
        InputStream inputStream = null;
        try {
            inputStream = context.getResources().getAssets().open(assertFileName);
            outputStream = new FileOutputStream(filePath);
            byte[] buffer = new byte[1024];
            int length = 0;
            while ((length = inputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    MyUtil工具类中创建文件夹的代码如下:

      /**
     * 获得存储文件
     *
     * @param
     * @param
     * @return
     */
    public static File getCacheFile(Context context,String name) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
    
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + name);
    }

    5.2 配置语音合成客户端:

    /**
    * 运用的配置信息,由百度语音官网给定
    */
    public static final String API_KEY = "hBuMkBgzGR0YQQHflkufAWcvFRaqTxxx";
    public static final String SECRET_KEY = "FemUYlEKxzr0moQ0jydZcmQ3fo11xxx";
    public static final String APP_ID = "9369xxx";
    
     /**
     * 语音合成的客户端
     */
    private SpeechSynthesizer speechSynthesizer;
    /**
     * 合成状态的监听器
     */
    private SpeechSynthesizerListener speechSynthesizerListener;
    
    /**
     * 初始化语音的client
     */
    private void initBaiduTts(Context context) {
        speechSynthesizer = SpeechSynthesizer.getInstance();
        speechSynthesizer.setContext(context);
        //设置监听器
        if (getSpeechSynthesizerListener() != null) {
            speechSynthesizer.setSpeechSynthesizerListener(getSpeechSynthesizerListener());
        }
        //设置运用的api_key和secret_key
        speechSynthesizer.setApiKey(API_KEY, SECRET_KEY);
        //设置运用的app_id
        speechSynthesizer.setAppId(APP_ID);
        File dirFile = getDirFile(context);
        //设置语音合成中文本模型文件(离线使用)
        speechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, getFilePath(dirFile, TEXT_MODEL_NAME));
        //设置语音合成中声音模型文件(离线使用)
        speechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, getFilePath(dirFile, SPEECH_FEMALE_MODE_NAME));
        //发音人(在线引擎),可用参数为0,1,2,3。。。(服务器端会动态增加,各值含义参考文档,以文档说明为准。0--普通女声,1--普通男声,2--特别男声,3--情感男声。。。)
        speechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");
        //设置Mix模式的合成策略
        speechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);
        //上线的时候,移除授权检查操作,即移除以下if语句
        if (isAuthSuccess()) {
            LogController.i(BaiduTtsController.class.getSimpleName(), "首次验证成功");
        }
        //初始化tts
        speechSynthesizer.initTts(TtsMode.MIX);
    
    }
    
    /**
     * 用于首次使用的使用,测试检查运用是成功申请到授权。注意点:若是测试无误后,可省略该步骤。
     *
     * @return
     */
    public boolean isAuthSuccess() {
        AuthInfo info = speechSynthesizer.auth(TtsMode.MIX);
        if (!info.isSuccess()) {
            LogController.i(BaiduTtsController.class.getSimpleName(), "错误信息: " + info.getTtsError().getMessage());
        }
        return info.isSuccess();
    }
    
    public SpeechSynthesizerListener getSpeechSynthesizerListener() {
        return speechSynthesizerListener;
    }
    
    /**
     * 设置语音合成的监听器
     *
     * @param speechSynthesizerListener
     */
    public void setSpeechSynthesizerListener(SpeechSynthesizerListener speechSynthesizerListener) {
        this.speechSynthesizerListener = speechSynthesizerListener;
        if(speechSynthesizerListener!=null){
            this.speechSynthesizer.setSpeechSynthesizerListener(this.speechSynthesizerListener);
        }
    }

    5.3 语音合成客户端的生命周期管理:

    在Activity或者Service的生命周期中使用,onCreate()调用客户端的初始化操作(即以上两个配置步骤), onResume()调用客户端的resume(),Onstop()中调用客户端的stop().最后在onDestory()中调用客户端的release()

    
    /**
     * 释放资源
     */
    public void release() {
        this.speechSynthesizer.release();
    }
    
    /**
     * 停止正在执行的任务
     */
    public void stop() {
        this.speechSynthesizer.stop();
    }
    
    /**
     * 恢复暂停的任务
     */
    public void resume() {
        this.speechSynthesizer.resume();
    }

    5.4 通过语音合成的客户端,将多段文本按一定顺序或者单独一段文本进行播放:

    /**
     * 将text转成一个语音文件,然后自动播放。
     *
     *
     * @param text
     */
    public void speeckText(String text) {
        if (!TextUtils.isEmpty(text)) {
            this.speechSynthesizer.speak(text);
        }
    
    }
    
    /**
     * 有顺序的合成多段语音,然后按一定顺序播放。
     *
     * @param values
     */
    public void speeckTextValues(String[] values) {
        if (values == null && values.length == 0) {
            return;
        }
        List<SpeechSynthesizeBag> bags = new ArrayList<>();
        for (int i = 0; i < values.length; ++i) {
            bags.add(getSpeechSynthesizeBag(values[i], String.valueOf(i)));
        }
        this.speechSynthesizer.batchSpeak(bags);
    }
    
    /**
     *
     * @param text 播放的文本内容
     * @param utterancedId  播放顺序,即第几个播放
     * @return
     */
    private SpeechSynthesizeBag getSpeechSynthesizeBag(String text, String utterancedId) {
        SpeechSynthesizeBag speechSynthesizeBag = new SpeechSynthesizeBag();
        //需要合成的文本text的长度不能超过1024个GBK字节。
        speechSynthesizeBag.setText(text);
        speechSynthesizeBag.setUtteranceId(utterancedId);
        return speechSynthesizeBag;
    }

    5.5 监控语音合成客户端的监听器(即SpeechSynthesizerListener)

    public class MessageDialogActivity extends BaseActivity implements View.OnClickListener,SpeechSynthesizerListener {
    
    private static final String TAG=MessageDialogActivity.class.getSimpleName();
    
    @Override
    public void onSynthesizeStart(String s) {
        LogController.i(TAG," onSynthesizeStart "+s);
    }
    
    @Override
    public void onSynthesizeDataArrived(String s, byte[] bytes, int i) {
        LogController.i(TAG," onSynthesizeDataArrived "+s);
    }
    
    @Override
    public void onSynthesizeFinish(String s) {
        LogController.i(TAG," onSynthesizeFinish "+s);
    }
    
    @Override
    public void onSpeechStart(String s) {
        LogController.i(TAG," onSpeechStart "+s);
    }
    
    @Override
    public void onSpeechProgressChanged(String s, int i) {
        LogController.i(TAG," onSpeechProgressChanged "+s);
    }
    
    @Override
    public void onSpeechFinish(String s) {
        LogController.i(TAG," onSpeechFinish "+s);
    }
    
    @Override
    public void onError(String s, SpeechError speechError) {
        LogController.i(TAG," onError "+speechError.description);
    }
    }

(个人)在实际项目中做法:

  1. 将百度语音客户端的初始化操作,生命周期管理操作,播放语音操作,封装成一个操作类。

    public class BaiduTtsController {
    public static BaiduTtsController instance;
    
    private BaiduTtsController(Context context) {
        initConfig(context);
    }
    
    public synchronized static BaiduTtsController getInstance(Context context) {
        return instance = instance == null ? new BaiduTtsController(context) : instance;
    }
    
    public void initConfig(Context context) {
        initFileConfig(context);
        initBaiduTts(context);
    }
    //剩下的是,初始化,生命周期,播放的具体代码,以上步骤已经贴出。
    }
  2. 将这个操作类保存到自定义的Application子类中,作为一个全局共享的对象。

    public class BaseApplication extends Application {
    
    private BaiduTtsController controller;
    public  BaiduTtsController  initBaiduTts(){
       return controller= controller==null?BaiduTtsController.getInstance(this):controller;
    }
    public BaiduTtsController getBaiduTtsController(){
        return controller;
    }
    public void setBaiduTtsController(BaiduTtsController controller){
        this.controller=controller;
    }
    }
  3. 在service中通过Application子类对象,获取到操作类对象,拿到需要播放的文本,调用播放语音的操作。

    private BaiduTtsController controller;
    //获取操作类的对象
    controller=BaseApplication.getAppContext().getBaiduTtsController();
    controller.setSpeechSynthesizerListener(this);
    
    //播放文本
    controller.speeckText(msg1);

开发遇到问题:

  • so库没有加载成功,控制台日志如下:
  No implementation found for int com.baidu.tts.jni.EmbeddedSynthesizerEngine.bdTTSGetLicense

   No implementation found for int com.baidu.speechsynthesizer.utility.SpeechDecoder.decodeWithCallback
 解决方式:正确导入so库,可以在手机中安装的运用查看so库是否随运用一起安装在手机上。
  • 运用的签名错误,控制台日志如下:

        Authentication Error
        =============================================
          ----------------- 鉴权错误信息 ------------
        sha1;package:B3:88:6A:66:18:70:4E:FE:48:60:4C:4C:37:A9:2D:84:5D:16:56:xx;com.xxx.mjqzclient
        key:RWpAlKWQE8SBoE354xUYeRcy906Asxxx
        errorcode: -11 uid: -1 appid -1 msg: httpsPost failed,IOException:Unable to resolve host "api.map.baidu.com": No address associated with hostname
        请仔细核查 SHA1、package与key申请信息是否对应,key是否删除,平台是否匹配
        errorcode为230时,请参考论坛链接:
        http://bbs.lbsyun.baidu.com/forum.php?mod=viewthread&tid=106461

    解决方式:正确配置App的签名,然后在开发平台上,填写对应的包名,和sha1值。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐