Android TTS系列一如何让app具备tts能力

2020-12-23 15:14:23

参考地址 Android TTS系列一——如何让app具备tts能力

源码地址:https://github.com/yellowgreatsun/MXTtsEngine

自2016年阿尔法狗大胜李世石以来,AI迅速普及,而其中语言AI就是落地化比较彻底的应用,也自然成了许多应用、智能硬件的标配功能。本人目前主要从事Android开发,2018年曾对tts做了比较多的研究,这几天,就想把之前的笔记再好好整理一番,写成博客分享给更多的开发者。

Android TTS系列

  • 如何让app具备tts能力?

  • 如何开发一款系统级tts引擎?

  • Android speech包源码剖析

本篇就先从第一项说起。
对应着MXTtsEngine的bdtts包和speech包。

让app具备tts能力,有两种方式,一种是直接集成第三方tts sdk,比如科大讯飞、百度、思必驰、小爱等等,调用sdk的接口即可。第二种是直接调用Android speech包下的tts接口,但其前提是Android设备已经安装了tts引擎。

一、直接集成第三方tts sdk

这里,以集成百度tts sdk为例。其它几种sdk,集成方式类似。

申请账号信息
  1. 进入百度AI开发平台,从“产品服务”中选择“语言合成”,可以查看官方介绍、在线体验合成效果。

  2. 进入控制台,在“产品服务”中选择“百度语音”后,点击“创建应用”,然后根据提示一步步填写信息,直至创建完成。而后,就可以看到所创建应用到账号信息,包括AppID、API Key、Secret Key,后面会用到。

集成tts sdk

如何集成tts sdk,详细信息可以查看官方文档。本文会进行简要介绍,代码可参考bdtts包下的BaiduTtsActivity.java。

  1. 集成jar包、so及其它资源。其中assets包下为离线语音包。


    集成tts sdk资源包.png

  2. 初始化引擎。
    创建引擎实例、设置listener、设置所需要的参数(比如发音人、音量、音速、音调、音频流类型、是否压缩)。

  private void initEngine() {

        // 1. 获取实例
        mSpeechSynthesizer = SpeechSynthesizer.getInstance();
        mSpeechSynthesizer.setContext(this);
        // 2. 设置listener
        mSpeechSynthesizer.setSpeechSynthesizerListener(speechSynthesizerListener);
        // 3. 设置appId,appKey.secretKey
        mSpeechSynthesizer.setAppId(appId);
        mSpeechSynthesizer.setApiKey(appKey, secretKey);
        // 4. 支持离线的话,需要设置离线模型
        if (ttsMode.equals(TtsMode.MIX)) {
            // 检查离线授权文件是否下载成功,离线授权文件联网时SDK自动下载管理,有效期3年,3年后的最后一个月自动更新。
            if (!checkAuth()) {
                return;
            }
            // 文本模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在并且可读
            mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, mApp.getTextModeFile());
            // 声学模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在并且可读
            mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, mApp.getSpeechModeFile());
        }
        // 5. 以下setParam 参数选填。不填写则默认值生效
        // 设置在线发声音人: 0 普通女声(默认) 1 普通男声 2 特别男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
        mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "4");
        // 其它参数设置这里略去
        // 6. 初始化
        mSpeechSynthesizer.initTts(ttsMode);
    }
  1. 语音播放或仅语言合成
    语音播放调用接口mSpeechSynthesizer.speak(text)。

mSpeechSynthesizer.speak("青青子衿,悠悠我心。");

仅语音合成调用接口mSpeechSynthesizer.synthesize(text),在回调中获取语音流。

mSpeechSynthesizer.synthesize("青青子衿,悠悠我心。");

语音流合成的开始、过程、结束;语音播放的开始、过程、结束;出现错误均在listener中回调。

  SpeechSynthesizerListener speechSynthesizerListener = new SpeechSynthesizerListener() {
        @Override
        public void onSynthesizeStart(String s) {
            //合成开始
            if (isNeedSaveTTS) {
                String filename = TimeUtil.getTimeStampLocal() + ".pcm";
                ttsFile = new File(destDir, filename);
                try {
                    if (ttsFile.exists()) {
                        ttsFile.delete();
                    }
                    ttsFile.createNewFile();
                    FileOutputStream ttsFileOutputStream = new FileOutputStream(ttsFile);
                    ttsFileBufferedOutputStream = new BufferedOutputStream(ttsFileOutputStream);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public void onSynthesizeDataArrived(String s, byte[] data, int i) {
            // 合成过程中的数据回调接口
            if(isNeedSaveTTS){
                try {
                    ttsFileBufferedOutputStream.write(data);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public void onSynthesizeFinish(String s) {
            // 合成结束
            if (isNeedSaveTTS)
                close();
        }
        @Override
        public void onSpeechStart(String s) {
            // 播放开始
        }
        @Override
        public void onSpeechProgressChanged(String s, int i) {
            // 播放过程中的回调
        }
        @Override
        public void onSpeechFinish(String s) {
            // 播放结束
        }
        @Override
        public void onError(String s, SpeechError speechError) {
            // 合成和播放过程中出错时的回调
            if (isNeedSaveTTS)
                close();
        }
    };
  1. 释放引擎。
    调用stop、release接口。

   @Override
    protected void onDestroy() {
        if (mSpeechSynthesizer != null) {
            mSpeechSynthesizer.stop();
            mSpeechSynthesizer.release();
            mSpeechSynthesizer = null;
        }
        super.onDestroy();
    }

二、调用Android speech包下的接口

先看一下speech包。

speech包.png


Android从1.6起就提供了speech包,但是,speech包仅仅提供tts接口,而实现tts的能力,则需要安装第三方tts引擎。Android原生系统直接集成了pico引擎(仅支持英文合成),国内厂商也会集成一些tts引擎,比如我的小米手机集成了“小爱语音引擎”(早期的miui版本集成的是基于百度tts sdk开发的“语音合成引擎”)。具体可以到手机的 设置—语言和输入法—文字转语言(TTS)输出 下看一下支持哪些引擎,及默认引擎是哪一个。
自己从哪可以获取到tts引擎呢?我发现很难在应用商店或者官网上搜到,我自己收集到了几个引擎,上传到了百度云中。链接:https://pan.baidu.com/s/1E2OSb_jGG5dwm9YIe8_0QQ  密码:t8d8。


介绍了背景信息,接下来就开始看一下如何利用speech包来进行语言合成。代码可参考speech包下的TestSpeechActivity.java。

  1. 创建TextToSpeech对象,创建时传入OnInitListener监听器监听创建是否成功。

  textToSpeech = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int status) {
                // status : TextToSpeech.SUCCESS=0 , TextToSpeech.ERROR=-1
                Log.i(TAG, "TextToSpeech onInit status = " + status);
            }
        });

备注:创建TextToSpeech对象也可以调用包含指定包名的引擎,不指定的话就用默认引擎。

  1. 设置TextToSpeech所使用语言、国家选项,通过返回值判断TTS是否支持该语言、国家选项。

   int result = textToSpeech.setLanguage(Locale.CHINESE);

如果tts引擎不支持该语言,该接口就会返回TextToSpeech.LANG_MISSING_DATA或者result == TextToSpeech.LANG_NOT_SUPPORTED。

  1. 调用speak或synthesizeToFile方法,前者是语音播报,后者是仅语音合成。

mTTS.speak("青青子衿,悠悠我心", TextToSpeech.QUEUE_FLUSH, null,id);mTTS.synthesizeToFile("青青子衿,悠悠我心", null, file, id);

其结果会走setOnUtteranceProgressListener回调。

 textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
     @Override
     public void onStart(String utteranceId) {
     }
     @Override
      public void onDone(String utteranceId) {
      }
     @Override
      public void onError(String utteranceId) {
      }});
  1. 关闭TTS,回收资源。

    @Override
    protected void onDestroy() {
        // 4.关闭TTS,回收资源
        if (textToSpeech != null) {
            textToSpeech.stop();
            textToSpeech.shutdown();
        }
        super.onDestroy();
    }

三、二者比较

第一种是需要集成tts SDK,适合于为单个应用提供tts能力。
第二种是需要集成tts引擎,适合于集成到手机或其它Android设备中,这样该设备的所有应用均可以直接利用Android系统tts接口使用tts能力。

下一篇文章会介绍,如何开发tts引擎,敬请期待。




  • 2018-12-07 17:47:24

    linux中wc命令用法

    Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数、字数、行数,并将统计结果显示输出。

  • 2018-12-07 22:19:33

    修改 Nginx 进程最大可打开文件数(worker_processes和worker_connections)

    worker_processes:操作系统启动多少个工作进程运行Nginx。注意是工作进程,不是有多少个nginx工程。在Nginx运行的时候,会启动两种进程,一种是主进程master process;一种是工作进程worker process。例如我在配置文件中将worker_processes设置为4,启动Nginx后,使用进程查看命令观察名字叫做nginx的进程信息,我会看到如下结果:

  • 2018-12-07 22:55:02

    nginx worker_processes 配置

    据另一种说法是,nginx开启太多的进程,会影响主进程调度,所以占用的cpu会增高, 这个说法我个人没有证实,估计他们是开了一两百个进程来对比的吧。