草庐IT

Android 离线文字转语音功能-TTS(Text To Speech)

ansondroider 2023-11-01 原文

前言

     在 Android 中,TTS全称叫做 Text to Speech,从字面就能理解它解决的问题是什么,把文本转为语音服务,意思就是你输入一段文本信息,然后Android 系统可以把这段文字播报出来。这种应用场景目前比较多是在各种语音助手APP上,很多手机系统集成商内部都有内置文本转语音服务,可以读当前页面上的文本信息。同样,在一些阅读类APP上我们也能看到相关服务,打开微信读书,里面就直接可以把当前页面直接用语音方式播放出来,特别适合哪种不方便拿着手机屏幕阅读的场景。

     Android系统从1.6版本开始就支持TTS, 不过遗憾的是系统默认的TTS引擎:Pico TTS,并不支持中文。

     在查找了许相关资料后, 发现很多的资源都已经失效, APK找不到, 对应的页面404. 更多的厂商则转成了开放平台甚至不再提供离线/免费服务.

目前找到的离线TTS

注:下面的链接可能需要科学上网才可以访问

应用名称支持离线备注下载地址
ITRI TTS下载
讯飞语记需打开一次, 但不需要登陆下载
科大讯飞语音引擎3.0推荐下载
Speech Services by Google需要科学上网, 离线需要先下载语音包下载

使用

首先, 安装对应的TTS应用, 再在设置中设置对应的语音输出服务
一般设置的步骤为: 设置>无障碍>文字转语音(TTS)输出

程序调用只需使用SDK标准的TTS接口即可.
在介绍TTS使用的文章中, 下面这一段的代码, 对于前面的科大讯飞或ITRI TTS并没什么效果, 只是多了一个弹窗, 当然, 也可以理解为, 这两个APP并没有按标准接口开发.

//显示应用选择窗.
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, MY_DATA_CHECK_CODE);

private TextToSpeech mTts;
protected void onActivityResult(
        int requestCode, int resultCode, Intent data) {
    if (requestCode == MY_DATA_CHECK_CODE) {
        if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
            // success, create the TTS instance
            mTts = new TextToSpeech(this, this);
        } else {
            // missing data, install it
            Intent installIntent = new Intent();
            installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
            startActivity(installIntent);
        }
    }
}

在程序中打印TTS所支持的语言及声音getAvailableLanguagesgetVoices:

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            Set<Locale> ava = mTts.getAvailableLanguages();
            if (ava != null) {
                for (Locale l : ava) {
                    Logger.v(TAG, "supported " + l.toString());
                }
            } else {
                Logger.d(TAG, "no supported language");
            }
            Set<Voice> voices = mTts.getVoices();
            if(voices != null){
                for(Voice v : voices){
                    Logger.d(TAG, "TTS Voice: " + v.toString());
                }
            }
        }
        String engine = mTts.getDefaultEngine();
        Logger.d(TAG, "TTS Engine = " + engine);
2022-10-18 15:21:22.577 25266-25266 TextToSpeech       com.tts.test         I  Connected to ComponentInfo{tw.pu.tts/tw.pu.tts.UttsService}
2022-10-18 15:21:22.582 25266-26006 TextToSpeech       com.tts.test         I  Set up connection to ComponentInfo{tw.pu.tts/tw.pu.tts.UttsService}
2022-10-18 15:21:22.594 25266-25266 TTS                com.tts.test         D   onInit 0
2022-10-18 15:21:22.650 25266-25266 TTS                com.tts.test         V   supported zh
2022-10-18 15:21:22.650 25266-25266 TTS                com.tts.test         V   supported en
2022-10-18 15:21:22.650 25266-25266 TTS                com.tts.test         V   supported zh__#Hans
2022-10-18 15:21:22.650 25266-25266 TTS                com.tts.test         V   supported zh__#Hant
2022-10-18 15:21:22.690 25266-25266 TTS                com.tts.test         D   TTS Voice: Voice[Name: zh, locale: zh__#Hans, quality: 300, latency: 300, requiresNetwork: false, features: []]
2022-10-18 15:21:22.691 25266-25266 TTS                com.tts.test         D   TTS Voice: Voice[Name: en, locale: en, quality: 300, latency: 300, requiresNetwork: false, features: []]
2022-10-18 15:21:22.691 25266-25266 TTS                com.tts.test         D   TTS Voice: Voice[Name: zh, locale: zh, quality: 300, latency: 300, requiresNetwork: false, features: []]
2022-10-18 15:21:22.691 25266-25266 TTS                com.tts.test         D   TTS Voice: Voice[Name: zh, locale: zh__#Hant, quality: 300, latency: 300, requiresNetwork: false, features: []]
2022-10-18 15:21:22.698 25266-25266 TTS                com.tts.test         D   TTS Engine = tw.pu.tts

注: TTS的功能需要在TextToSpeech.OnInitListener回调, 并确认状态正常后才可以开始正常工作

完整代码TTS.java

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.speech.tts.TextToSpeech;
import android.speech.tts.Voice;

import java.util.Locale;
import java.util.Set;

public class TTS implements TextToSpeech.OnInitListener {
    final String TAG = "TTS";
    TextToSpeech mTts;

    public TTS(Context context){
        mTts = new TextToSpeech(context, this);
    }

    public TTS(Context context, TextToSpeech.OnInitListener lis){
        mTts = new TextToSpeech(context, this);
        setOnInitListener(lis);
    }

    final int DATA_CHECK_CODE = 0x774;
    @Deprecated
    public TTS(Activity activity){
        Intent checkIntent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
        activity.startActivityForResult(checkIntent, 0x774);
    }
    @Deprecated
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
        if (requestCode == DATA_CHECK_CODE) {
            if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
                // success, create the TTS instance
                mTts = new TextToSpeech(activity, this);
            } else {
                // missing data, install it
                Intent installIntent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
                activity.startActivity(installIntent);
            }
        }
    }

    public TextToSpeech getTts(){return mTts;}

    private void dump(){
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            Set<Locale> ava = mTts.getAvailableLanguages();
            if (ava != null) {
                for (Locale l : ava) {
                    Logger.v(TAG, "supported " + l.toString());
                }
                //mTts.setLanguage(Locale.US);
            } else {
                Logger.d(TAG, "no supported language");
            }
            Set<Voice> voices = mTts.getVoices();
            if(voices != null){
                for(Voice v : voices){
                    Logger.d(TAG, "TTS Voice: " + v.toString());
                }
            }
        }
        String engine = mTts.getDefaultEngine();
        Logger.d(TAG, "TTS Engine = " + engine);
    }

    public void setSpeechSpeed(float speed){
        mTts.setSpeechRate(speed);
    }
    public void setVoiceFreq(float freq){
        mTts.setPitch(freq);
    }

    public void speak(String s){
        if(mTts == null){
            Logger.e(TAG, "speak failed: TTS not ready");
            return;
        }
        int res = mTts.speak(s, TextToSpeech.QUEUE_ADD, null);
        if(res != 0){
            Logger.w(TAG, "speak failed: " + res);
        }else{
            Logger.d(TAG, "speak " + s + " done");
        }
    }

    public void stop(){
        mTts.stop();
    }

    @Override
    public void onInit(int status) {
        Logger.d(TAG, "onInit " + status);
        if(status == 0) {
            dump();
            mTts.setPitch(1f);
            mTts.setSpeechRate(1f);
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                AudioAttributes attr = new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
                mTts.setAudioAttributes(attr);
            }
        }

        if(initLis != null)initLis.onInit(status);
    }

    TextToSpeech.OnInitListener initLis;
    public void setOnInitListener(TextToSpeech.OnInitListener initLis){
        this.initLis = initLis;
    }
}

参考

Android 中文語音引擎資源
Android中使用自带TextToSpeech实现离线语音合成功能
The 7 Best Text-to-Speech Apps for Android
Android中提供的免费文字转语音功能TextToSpeech之快速入门用法(Android TTS 语音合成播报)
An introduction to Text-To-Speech in Android

附:
文字转语音(TTS) 应用 (APK)

有关Android 离线文字转语音功能-TTS(Text To Speech)的更多相关文章

  1. ruby - 完全离线安装RVM - 2

    我打算为ruby​​脚本创建一个安装程序,但我希望能够确保机器安装了RVM。有没有一种方法可以完全离线安装RVM并且不引人注目(通过不引人注目,就像创建一个可以做所有事情的脚本而不是要求用户向他们的bash_profile或bashrc添加一些东西)我不是要脚本本身,只是一个关于如何走这条路的快速指针(如果可能的话)。我们还研究了这个很有帮助的问题:RVM-isthereawayforsimpleofflineinstall?但有点误导,因为答案只向我们展示了如何离线在RVM中安装ruby。我们需要能够离线安装RVM本身,并查看脚本https://raw.github.com/wayn

  2. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

  3. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  4. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  5. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  6. ruby-on-rails - rails 功能测试 - 2

    在Rails自动生成的功能测试(test/functional/products_controller_test.rb)中,我看到以下代码:classProductsControllerTest我的问题是:方法调用products()在哪里/如何定义?products(:one)到底是什么意思?看代码,大概意思是“创建一个产品”,但是它是如何工作的呢?注意我是Ruby/Rails的新手,如果这些是微不足道的问题,我深表歉意。 最佳答案 如果您查看test/fixtures文件夹,您会看到一个products.yml文件。这是在您创建

  7. ruby - 字符串文字前面的 * 在 ruby​​ 中有什么作用? - 2

    这段代码似乎创建了一个范围从a到z的数组,但我不明白*的作用。有人可以解释一下吗?[*"a".."z"] 最佳答案 它叫做splatoperator.SplattinganLvalueAmaximumofonelvaluemaybesplattedinwhichcaseitisassignedanArrayconsistingoftheremainingrvaluesthatlackcorrespondinglvalues.Iftherightmostlvalueissplattedthenitconsumesallrvaluesw

  8. ruby-on-rails - 功能测试 Authlogic? - 2

    在我的一些Controller中,我有一个before_filter检查用户是否登录?用于CRUD操作。application.rbdeflogged_in?unlesscurrent_userredirect_toroot_pathendendprivatedefcurrent_user_sessionreturn@current_user_sessionifdefined?(@current_user_session)@current_user_session=UserSession.findenddefcurrent_userreturn@current_userifdefine

  9. ruby - Ruby 中允许 "p *1..10"打印出数字 1-10 的功能是什么? - 2

    require'pp'p*1..10这会打印出1-10。为什么这么简洁?您还可以用它做什么? 最佳答案 它是“splat”运算符。它可用于分解数组和范围并在赋值期间收集值。这里收集赋值中的值:a,*b=1,2,3,4=>a=1b=[2,3,4]在此示例中,内部数组([3,4])中的值被分解并收集到包含数组中:a=[1,2,*[3,4]]=>a=[1,2,3,4]您可以定义将参数收集到数组中的函数:deffoo(*args)pargsendfoo(1,2,"three",4)=>[1,2,"three",4]

  10. ruby - 现代计算机的功能是否不足以处理字符串而无需使用符号(在 Ruby 中) - 2

    我读过的关于Ruby符号的每一篇文章都在谈论符号相对于字符串的效率。但是,这不是1970年代。我的电脑可以处理一些额外的垃圾收集。我错了吗?我拥有最新最好的奔腾双核处理器和4GBRAM。我认为这应该足以处理一些字符串。 最佳答案 您的计算机可能能够处理“一点点额外的垃圾收集”,但是当“一点点”发生在运行数百万次的内部循环中时呢?如果它在内存有限的嵌入式系统上运行呢?有很多地方你可以随意使用字符串,但在某些地方你不能。这完全取决于上下文。 关于ruby-现代计算机的功能是否不足以处理字符串

随机推荐