美文网首页
离线语音播报解决方案

离线语音播报解决方案

作者: c538e90538d4 | 来源:发表于2019-08-20 22:11 被阅读0次

背景

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

因为当时项目时间比较紧,首先想到的是接入一个语音合成的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)输出这一步比较麻烦,需要人为设置

设置文字转语音(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<String, Uri>();
        mUtterances = new HashMap<CharSequence, Uri>();
        mUtteranceProgressListener = null;

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

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

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

附完整代码TTSHelper:


/**
 * tts 工具类
 * <p>
 * 原生TTS + 中文语音引擎(这里选择的是科大讯飞)
 * <p>
 */
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;
        }

    }





}

相关文章

网友评论

      本文标题:离线语音播报解决方案

      本文链接:https://www.haomeiwen.com/subject/sgmmsctx.html