离线语音播报解决方案

背景

超市的自助收银的项目,为了更好的引导顾客完成买单操作,需要用到语音播报的功能

因为当时项目时间比较紧,首先想到的是接入一个语音合成的sdk,网上大概搜了下,方案相对成熟的科大讯飞,百度语音...类似这样的sdk很多,因为项目时间比较紧张,来不及做各种参数的对比了,于是通过选择用抓阄选择了百度语音sdk,匆匆上马,sdk集成没有难度,细节忽略...

一段时间后,一些新的商户涌现,新的需求出现了,有部分商户说他们超市不开放外网,尴尬的是,百度语音sdk是不支持纯离线的,虽然被明确告知不支持纯离线,但还是想试试

尝试一:
因为超市的场景是固定的,那找一台已经播放过所有场景的机器,把语音包内置到asset下面,初始化的时候再拷贝到内存下面,应该就可以把!!!

鉴权失败.png

第一次尝试失败,难受~

尝试二:

那如何绕过鉴权呢?跟踪代码发现,鉴权的操作是放在so,此时,心已经凉了一半,抱着试试看的态度,去网上搜了下,还真的有惊喜

百度语音离线合成授权破解

花了5个C币,下载下来,jar和so替换上,结果发现并没有什么软用...
这篇文章解决的是授权过期的问题,但是仍然存在一个致命的问题,首次离线,仍然需要联网鉴权(过段时间需要联网鉴权),它只解决了一部分问题

至此,百度语音sdk已经耗尽了我的耐心...当然也并不想去看其他的SDK了,基本违背了我司核心研发理念 不花钱

解决方案:原生TTS + 文字转语音引擎

山重水复疑无路,柳暗花明又一村,意外的发现,其实原生的android TTS本就支持文本转语音,but 对中文支持的不太友好,要想使用,需要配合文字转语音引擎,国内的一些品牌手机的rom都已经内置了相关文字转语音引擎,so直接使用就可以。

忧桑的是,我们用的机器定制的rom并不支持,只有原生的pico,这咋办?只能安装第三方的文字转语音

下面针对第三方的引擎做个简单对比

com.svox.pico 系统自带不支持中文语音
com.svox.classic 搜svox搜到的,和上面类似不支持中文
com.google.android.tts 谷歌文字转语音引擎,不支持5.0以下系统,大小17.98M
com.iflytek.speechcloud 科大讯飞语音引擎3.0,支持4.0以上系统,大小27.27M
com.iflytek.speechsuite 新版科大讯飞语音引擎,2018年开始新版手机一般会内置,如oppo、vivo、华为
com.baidu.duersdk.opensdk **度秘语音引擎3.0 ** 不支持5.0以下系统,大小11.95M
com.iflytek.tts 科大讯飞语音合成,较老,不支持7.0以上系统,大小9M
另外,科大讯飞引擎3.0安装后的名字叫:语音设置

百度网盘下载地址 密码:3si0

三步走:

  1. 下载
  2. 安装
  3. 设置文字转语音(TTS)输出

因为部分超市门店网络不稳定,下载需要做的相对稳定一些,失败自动重试,下载完成校验md5值等

设置文字转语音(TTS)输出这一步比较麻烦,需要人为设置

离线语音播报解决方案_第1张图片
设置文字转语音(TTS)输出.png

虽然这一步看起来很简单,但是门店的人员是不太愿意操作这么做的...太麻烦

那有没有可以修改这个默认选项的呢?有,修改framework

这似乎有点扯,继续google,还发现有个这样的API

  @Deprecated
    public int setEngineByPackageName(String enginePackageName) {
        mRequestedEngine = enginePackageName;
        return initTts();
    }

但是被废弃,这么优秀的方法居然被废弃了,应该还有替代者的

 /**
     * Used by the framework to instantiate TextToSpeech objects with a supplied
     * package name, instead of using {@link android.content.Context#getPackageName()}
     *
     * @hide
     */
    public TextToSpeech(Context context, OnInitListener listener, String engine,
            String packageName, boolean useFallback) {
        mContext = context;
        mInitListener = listener;
        mRequestedEngine = engine;
        mUseFallback = useFallback;

        mEarcons = new HashMap();
        mUtterances = new HashMap();
        mUtteranceProgressListener = null;

        mEnginesHelper = new TtsEngines(mContext);
        initTts();
    }

其实,初始化TextToSpeech的时候是可以指定文字转语音输出(TTS)引擎的

以上就是我的解决方案,虽然不够完美,但是解决了我的需求,分享给有需要的同学,同时也记录下自己的思考过程

附完整代码TTSHelper:


/**
 * tts 工具类
 * 

* 原生TTS + 中文语音引擎(这里选择的是科大讯飞) *

*/ public class TTSHelper { private static volatile TTSHelper ttsHelper = null; private static String FLYTEK_APK_URL = "http://xxx/flytek3.0.apk"; private static String FLYTEK_APK_NAME = "flytek3.0.apk"; private static String FLYTEK_APK_DIR = "TTS"; private static String FLYTEK_PACKAGE_NAME = "com.iflytek.speechcloud"; private static String FLYTEK_APK_MD5 = "5402c622c319fac17c50fe52581cb627"; private TextToSpeech mSpeech; //安装监听 private InstallReceiver mInstallReceiver; //是否准备好 private boolean mIsReady = false; private Context mContext; /** * 初始化 * * @param context */ public void init(Context context) { mContext = context; mSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() { @Override public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { int supported = mSpeech.setLanguage(Locale.CHINA); if ((supported != TextToSpeech.LANG_AVAILABLE) && (supported != TextToSpeech.LANG_COUNTRY_AVAILABLE)) { downloadFlytekApk(context); } else { mIsReady = true; LogFileUtils.writeLog(context,"\n科大讯飞引擎初始化成功'"); } } else { LogFileUtils.writeLog(context,"\n科大讯飞引擎初始化失败'"); } } }, FLYTEK_PACKAGE_NAME); mSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() { @Override public void onStart(String utteranceId) { //这个是开始的时候。是先发声之后才会走这里 //调用isSpeaking()方法在这为true } @Override public void onDone(String utteranceId) { //这个是播报完毕的时候 每一次播报完毕都会走 } @Override public void onError(String utteranceId) { LogFileUtils.writeLog(ContextUtils.getContext(), utteranceId); //错误 } }); } public static TTSHelper getInstance() { if (ttsHelper == null) { synchronized (TTSHelper.class) { if (ttsHelper == null) { ttsHelper = new TTSHelper(); } } } return ttsHelper; } /** * 播放语音 * * @param content */ public void speak(final String content) { if (mIsReady) { int supported = mSpeech.isLanguageAvailable(Locale.CHINA); if ((supported != TextToSpeech.LANG_AVAILABLE) && (supported != TextToSpeech.LANG_COUNTRY_AVAILABLE)) { mSpeech = new TextToSpeech(mContext, new TextToSpeech.OnInitListener() { @Override public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { int supported = mSpeech.setLanguage(Locale.CHINA); if ((supported != TextToSpeech.LANG_AVAILABLE) && (supported != TextToSpeech.LANG_COUNTRY_AVAILABLE)) { LogFileUtils.writeLog(mContext,"\n科大讯飞引擎初始化不支持中文'"); } else { LogFileUtils.writeLog(mContext,"\n科大讯飞引擎初始化成功'"); mSpeech.speak(content, TextToSpeech.QUEUE_FLUSH, null); } } else { LogFileUtils.writeLog(mContext,"\n科大讯飞引擎初始化失败'"); } } }, FLYTEK_PACKAGE_NAME); } else { mSpeech.speak(content, TextToSpeech.QUEUE_FLUSH, null); } } } /** * 下载科大讯飞3.0引擎 * 安装 */ public void downloadFlytekApk(Context context) { LogFileUtils.writeLog(mContext,"\n科大讯飞引擎开始下载'"); String destinationUrl = SlientDownloadTool.getInstance().getDownloadDir(context, FLYTEK_APK_DIR).getAbsolutePath() + File.separator + FLYTEK_APK_NAME; SlientDownloadTool.getInstance().startDownload(context, FLYTEK_APK_URL, destinationUrl, FLYTEK_APK_MD5, new DownloadStatusListenerV1() { @Override public void onDownloadComplete(DownloadRequest downloadRequest) { registerInstallReceiver(context); LogFileUtils.writeLog(mContext,"\n科大讯飞引擎开始安装"); InstallUtil.install(destinationUrl, context); } @Override public void onDownloadFailed(DownloadRequest downloadRequest, int errorCode, String errorMessage) { LogFileUtils.writeLog(mContext,"\n科大讯飞引擎下载失败"+errorMessage); } @Override public void onProgress(DownloadRequest downloadRequest, long totalBytes, long downloadedBytes, int progress) { } }); } /** * 注册应用安装卸载 * * @param context */ private void registerInstallReceiver(Context context) { IntentFilter intentFilter = new IntentFilter(); mInstallReceiver = new InstallReceiver(); intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); intentFilter.addDataScheme("package"); context.registerReceiver(mInstallReceiver, intentFilter); } public class InstallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //接收安装广播 if (intent.getAction().equals("android.intent.action.PACKAGE_ADDED")) { String packageName = intent.getDataString(); LogUtils.e(packageName); if (packageName.contains(FLYTEK_PACKAGE_NAME)) { mIsReady = true; LogFileUtils.writeLog(mContext,"\n科大讯飞引擎安装成功"); } } } } /** * 释放资源 */ public void release(Context context) { if (null == context) { return; } if (null != mInstallReceiver) { context.unregisterReceiver(mInstallReceiver); mInstallReceiver = null; } if (mSpeech != null) { mSpeech.stop(); mSpeech.shutdown(); mSpeech = null; } } }

你可能感兴趣的:(离线语音播报解决方案)