背景
超市的自助收银的项目,为了更好的引导顾客完成买单操作,需要用到语音播报的功能
因为当时项目时间比较紧,首先想到的是接入一个语音合成的sdk,网上大概搜了下,方案相对成熟的科大讯飞,百度语音...类似这样的sdk很多,因为项目时间比较紧张,来不及做各种参数的对比了,于是通过选择用抓阄选择了百度语音sdk,匆匆上马,sdk集成没有难度,细节忽略...
一段时间后,一些新的商户涌现,新的需求出现了,有部分商户说他们超市不开放外网,尴尬的是,百度语音sdk是不支持纯离线的,虽然被明确告知不支持纯离线,但还是想试试
尝试一:
因为超市的场景是固定的,那找一台已经播放过所有场景的机器,把语音包内置到asset下面,初始化的时候再拷贝到内存下面,应该就可以把!!!
第一次尝试失败,难受~
尝试二:
那如何绕过鉴权呢?跟踪代码发现,鉴权的操作是放在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
三步走:
- 下载
- 安装
- 设置文字转语音(TTS)输出
因为部分超市门店网络不稳定,下载需要做的相对稳定一些,失败自动重试,下载完成校验md5值等
设置文字转语音(TTS)输出这一步比较麻烦,需要人为设置
虽然这一步看起来很简单,但是门店的人员是不太愿意操作这么做的...太麻烦
那有没有可以修改这个默认选项的呢?有,修改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;
}
}
}