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-02-01 22:09:18

    给linux添加回收站

    linux下常常使用rm,导致误操作删除一些重要的文档,很难恢复(当然也能恢复,不过比较复杂),所以在这种情况下,我们如果能给linux添加一个回收站就好多了,说白了,这个回收站机制也比较简单,就是使用 mv封装一个rm,简单的可以自己写个脚本,复杂点的就是在bin下添加这个封装命令就行了。

  • 2018-02-02 10:52:50

    IntelliJ IDEA 快捷键说明大全(中英对照、带图示详解)

    因为觉得网络上的 idea 快捷键不够详尽,所以特别编写了此篇文章,方便大家使用 idea O(∩_∩)O~ 其中的英文说明来自于 idea 的官网资料,中文说明主要来自于自己的领会和理解,英文说明只是作为参考。重要的快捷键会附带图示,进行详细的说明。

  • 2018-02-02 15:19:29

    贝塞尔曲线扫盲

    相信很多同学都知道“贝塞尔曲线”这个词,我们在很多地方都能经常看到。但是,可能并不是每位同学都清楚地知道,到底什么是“贝塞尔曲线”,又是什么特点让它有这么高的知名度。

  • 2018-02-04 23:46:16

    Android SQLite 升级数据库,在原有数据库的基础上添加一列

    原本以为很简单的问题,直接在原来创建数据库的语句中加上需要添加的列new_column,但是运行时发现,应用crash。原因是,原有数据库文件已经存在的情况下并不会重新创建数据库,也就是说此时数据库中并没有new_column列,这个时候,运行query()来查询数据库如果包括new_column列,就会导致应用crash。

  • 2018-02-06 22:23:01

    HTML5 Canvas 的事件处理

    DOM是Web前端领域非常重要的组成部分,不仅在处理HTML元素时会用到DOM,图形编程也同样会用到。比如SVG绘图,各种图形都是以DOM节点的形式插入到页面中,这就意味着可以使用DOM方法对图形进行操作。比如有一个<path id=”p1″>元素,可以直接用jquery增加click事件$(‘#p1’).click(function(){…})”。然而这种DOM处理方法在HTML5的Canvas里不再适用,Canvas使用的是另外一套机制,无论在Canvas上绘制多少图形,Canvas都是一个整体,图形本身实际都是Canvas的一部分,不可单独获取,所以也就无法直接给某个图形增加JavaScript事件。