[ipv6][Feat]讯飞语音合成升级成离线版

This commit is contained in:
chenfufeng
2025-04-23 19:43:08 +08:00
committed by yangyakun
parent dd0cd108dc
commit 0ff13a6a41
22 changed files with 939 additions and 4 deletions

View File

@@ -9,7 +9,7 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: ly.count.android.plugins.UploadSymbolsPlugin
apply from: rootProject.file('gradle/bytex/bytex.gradle')
//apply from: rootProject.file('gradle/bytex/bytex.gradle')
Properties properties = new Properties()
properties.load(project.rootProject.file("gradle.properties").newDataInputStream())
@@ -219,7 +219,7 @@ dependencies {
implementation project(':core:mogo-core-function-call')
implementation project(':core:mogo-core-utils')
implementation project(':core:mogo-core-res')
implementation project(':tts:tts-iflytek')
implementation project(':tts:tts-iflytek-offline')
androidTestImplementation project(':core:mogo-core-function-call')
androidTestImplementation project(':core:mogo-core-res')

View File

@@ -24,6 +24,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -1818,6 +1820,26 @@ public final class FileUtils {
}).start();
}
public static void writeFile(String path, byte[] bytes) {
boolean append = false;
try {
File file = new File(path);
if (file.exists()) {
append = true;
}else {
file.createNewFile();
}
FileOutputStream out = new FileOutputStream(path,true);//指定写到哪个路径中
FileChannel fileChannel = out.getChannel();
fileChannel.write(ByteBuffer.wrap(bytes)); //将字节流写入文件中
fileChannel.force(true);//强制刷新
fileChannel.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 文件拷贝监听

View File

@@ -61,11 +61,17 @@ public class AIAssist {
Class<?> clazz1 = null;
Class<?> clazz2 = null;
Class<?> clazz3 = null;
Class<?> clazz4 = null;
try {
clazz1 = Class.forName("com.mogo.tts.pad.PadTTS");
} catch (Exception ignored) {
}
try {
clazz4 = Class.forName("com.mogo.tts.iflytekoffline.IFlyTekOfflineTts");
} catch (Exception ignored) {
}
try {
clazz2 = Class.forName("com.mogo.tts.iflytek.IFlyTekTts");
} catch (Exception ignored) {
@@ -77,10 +83,12 @@ public class AIAssist {
if (clazz1 != null) {
mTTS = (IMogoTTS) clazz1.getConstructor().newInstance();
} else if (clazz2 != null) {
mTTS = (IMogoTTS) clazz2.getConstructor().newInstance();
} else if (clazz4 != null) {
mTTS = (IMogoTTS) clazz4.getConstructor().newInstance();
} else if (clazz3 != null) {
mTTS = (IMogoTTS) clazz3.getConstructor().newInstance();
} else {
mTTS = (IMogoTTS) clazz2.getConstructor().newInstance();
}
if (mTTS != null) {
mTTS.init(context);

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.mgintelligent.speech"
android:versionCode="1" >
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="29" />
</manifest>

View File

@@ -53,6 +53,7 @@ include ':libraries:mogo-speech'
include ':tts:tts-base'
include ':tts:tts-pad'
include ':tts:tts-iflytek'
include ':tts:tts-iflytek-offline'
include ':tts:tts-zhi'
// 测试DEBUG

1
tts/tts-iflytek-offline/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,65 @@
plugins {
id 'com.android.library'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
}
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
// buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode Integer.valueOf(VERSION_CODE)
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
//ARouter apt 参数
kapt {
useBuildCache = false
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation files('libs/AIKit.aar')
implementation rootProject.ext.dependencies.androidxappcompat
implementation rootProject.ext.dependencies.arouter
kapt rootProject.ext.dependencies.aroutercompiler
api rootProject.ext.dependencies.aiassist
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
implementation rootProject.ext.dependencies.ttsbase
implementation rootProject.ext.dependencies.mogo_core_utils
} else {
implementation project(":tts:tts-base")
implementation project(':core:mogo-core-utils')
}
}
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()

View File

@@ -0,0 +1,3 @@
GROUP=com.mogo.tts
POM_ARTIFACT_ID=tts-iflytek-offline
VERSION_CODE=1

Binary file not shown.

View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.tts.iflytekoffline">
</manifest>

View File

@@ -0,0 +1,229 @@
package com.mogo.tts.iflytekoffline
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioRecord
import android.media.AudioTrack
import android.os.Process
import java.io.DataInputStream
import java.io.File
import java.io.FileInputStream
class AudioTrackManager {
private var mAudioTrack: AudioTrack? = null
private var mDis: DataInputStream? = null //播放文件的数据流
private var mRecordThread: Thread? = null
private var isStart = false
//指定缓冲区大小。调用AudioRecord类的getMinBufferSize方法可以获得。
private var mMinBufferSize = 0
private var mSampleRate = 16000
private var listener: OnCompleteListener? = null
enum class sampleRateType {
SAMPLE_RATE_16k,
SAMPLE_RATE_24k
}
private fun initData() {
//根据采样率采样精度单双声道来得到frame的大小。
mMinBufferSize =
AudioTrack.getMinBufferSize(mSampleRate, mChannelConfig, mAudioFormat) //计算最小缓冲区
//注意按照数字音频的知识这个算出来的是一秒钟buffer的大小。
//创建AudioTrack
mAudioTrack = AudioTrack(
mStreamType, mSampleRate, mChannelConfig,
mAudioFormat, mMinBufferSize, mMode
)
}
fun setListener(listener: OnCompleteListener) {
this.listener = listener
}
fun setSampleRate(sampleRate: sampleRateType?) {
when (sampleRate) {
sampleRateType.SAMPLE_RATE_16k -> mSampleRate = mSampleRateIn16KHz
sampleRateType.SAMPLE_RATE_24k -> mSampleRate = mSampleRateIn24KHz
else -> {
mSampleRate = mSampleRateIn24KHz
}
}
}
/**
* 销毁线程方法
*/
private fun destroyThread() {
try {
isStart = false
if (null != mRecordThread && Thread.State.RUNNABLE == mRecordThread!!.state) {
try {
Thread.sleep(500)
mRecordThread!!.interrupt()
} catch (e: Exception) {
mRecordThread = null
}
}
mRecordThread = null
} catch (e: Exception) {
e.printStackTrace()
} finally {
mRecordThread = null
}
}
/**
* 启动播放线程
*/
private fun startThread() {
destroyThread()
isStart = true
if (mRecordThread == null) {
mRecordThread = Thread(playRunnable)
mRecordThread!!.start()
}
}
/**
* 播放线程
*/
var playRunnable: Runnable = Runnable {
try {
//设置线程的优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
val tempBuffer = ByteArray(mMinBufferSize)
var readCount = 0
while (mDis!!.available() > 0) {
readCount = mDis!!.read(tempBuffer)
if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
continue
}
if (readCount != 0 && readCount != -1) { //一边播放一边写入语音数据
//判断AudioTrack未初始化停止播放的时候释放了状态就为STATE_UNINITIALIZED
if (mAudioTrack!!.state == AudioTrack.STATE_UNINITIALIZED) {
initData()
}
mAudioTrack!!.play()
mAudioTrack!!.write(tempBuffer, 0, readCount)
}
}
stopPlay() //播放完就停止播放
} catch (e: Exception) {
e.printStackTrace()
}
listener?.onComplete()
}
init {
initData()
}
/**
* 播放文件
* @param path
* @throws Exception
*/
@Throws(Exception::class)
private fun setPath(path: String) {
val file = File(path)
mDis = DataInputStream(FileInputStream(file))
}
/**
* 启动播放
*
* @param path
*/
fun startPlay(path: String) {
try {
// //AudioTrack未初始化
// if(mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED){
// throw new RuntimeException("The AudioTrack is not uninitialized");
// }//AudioRecord.getMinBufferSize的参数是否支持当前的硬件设备
// else if (AudioTrack.ERROR_BAD_VALUE == mMinBufferSize || AudioTrack.ERROR == mMinBufferSize) {
// throw new RuntimeException("AudioTrack Unable to getMinBufferSize");
// }else{
setPath(path)
startThread()
// }
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 停止播放
*/
fun stopPlay() {
try {
destroyThread() //销毁线程
if (mAudioTrack != null) {
if (mAudioTrack!!.state == AudioRecord.STATE_INITIALIZED) { //初始化成功
mAudioTrack!!.stop() //停止播放
}
if (mAudioTrack != null) {
mAudioTrack!!.release() //释放audioTrack资源
}
}
if (mDis != null) {
mDis!!.close() //关闭数据输入流
}
} catch (e: Exception) {
e.printStackTrace()
}
}
val playState: Int
get() {
if (mAudioTrack != null) {
return mAudioTrack!!.playState
}
return AudioTrack.PLAYSTATE_STOPPED
}
companion object {
@Volatile
private var mInstance: AudioTrackManager? = null
//音频流类型
private const val mStreamType = AudioManager.STREAM_MUSIC
//指定采样率 MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100目前为常用的采样率官方文档表示这个值可以兼容所有的设置
const val mSampleRateIn16KHz: Int = 16000
const val mSampleRateIn24KHz: Int = 24000
//指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量
private const val mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO //单声道
//指定音频量化位数 ,在AudioFormaat类中指定了以下各种可能的常量。通常我们选择ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脉冲编码调制它实际上是原始音频样本。
//因此可以设置每个样本的分辨率为16位或者8位16位将占用更多的空间和处理能力,表示的音频也更加接近真实。
private const val mAudioFormat = AudioFormat.ENCODING_PCM_16BIT
//STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。这个和我们在socket中发送数据一样
// 应用层从某个地方获取数据例如通过编解码得到PCM数据然后write到audiotrack。
private const val mMode = AudioTrack.MODE_STREAM
val instance: AudioTrackManager?
/**
* 获取单例引用
*
* @return
*/
get() {
if (mInstance == null) {
synchronized(AudioTrackManager::class.java) {
if (mInstance == null) {
mInstance = AudioTrackManager()
}
}
}
return mInstance
}
}
interface OnCompleteListener {
fun onComplete()
}
}

View File

@@ -0,0 +1,512 @@
package com.mogo.tts.iflytekoffline
import android.content.Context
import android.os.Looper
import android.util.Pair
import androidx.annotation.Keep
import com.iflytek.aikit.core.AeeEvent
import com.iflytek.aikit.core.AiHandle
import com.iflytek.aikit.core.AiHelper
import com.iflytek.aikit.core.AiInput
import com.iflytek.aikit.core.AiListener
import com.iflytek.aikit.core.AiRequest
import com.iflytek.aikit.core.AiResponse
import com.iflytek.aikit.core.AiText
import com.iflytek.aikit.core.BaseLibrary
import com.iflytek.aikit.core.CoreListener
import com.iflytek.aikit.core.ErrType
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.d
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger.i
import com.mogo.eagle.core.utilcode.util.FileUtils
import com.mogo.eagle.core.utilcode.util.ResourceUtils
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.mogo.eagle.core.utilcode.util.UiThreadHandler
import com.mogo.tts.base.IGlobalTtsCallback
import com.mogo.tts.base.IMogoTTS
import com.mogo.tts.base.IMogoTTSCallback
import com.mogo.tts.base.LangTtsEntity
import com.mogo.tts.base.LanguageType
import com.mogo.tts.base.MultiLangTtsEntity
import com.mogo.tts.base.PreemptType
import com.mogo.tts.iflytekoffline.AudioTrackManager.OnCompleteListener
import java.io.File
import java.util.LinkedList
@Keep
class IFlyTekOfflineTts : IMogoTTS {
companion object {
const val TAG = "IFlyTekTts"
}
private var context: Context? = null
private var aiHandle: AiHandle? = null
private val OUTPUT_DIR by lazy {
"/sdcard/iflytek/xtts/output"
}
private val ABILITYID by lazy {
"e2e44feff"
}
// 等级由低到高为0、1、2、3默认为-1表示没有正在tts的
private var curTtsLevel = -1
// 由于主动打断不会有回调事件所以主动打断时清掉map中被打断的text和callback
private var curTtsContent = ""
private var curTtsEntity: MultiLangTtsEntity? = null
private val linkedList = LinkedList<Pair<MultiLangTtsEntity, Int>>()
private val speakVoiceMap by lazy {
HashMap<String, IMogoTTSCallback>()
}
private var mGlobalTtsCallback: IGlobalTtsCallback? = null
override fun init(context: Context) {
this.context = context
initSDK()
}
override fun initTts(sn: String?) {}
private fun initSDK() {
val workPath = "/sdcard/iflytek/aikit/xTTS"
val file = File("$workPath/e3fe94474_1.0.0_xTTS_CnCn_xiaoyan_2018_arm.irf")
if (!file.exists() || file.length() == 0L) {
ThreadUtils.getIoPool().execute {
ResourceUtils.copyFileFromAssets("xTTS", "$workPath")
initEngine(workPath)
}
} else {
ThreadUtils.getSinglePool().execute {
initEngine(workPath)
}
}
AiHelper.getInst().registerListener(coreListener)
AiHelper.getInst().registerListener(ABILITYID, aiRespListener)
}
private fun initEngine(workPath: String) {
val params = BaseLibrary.Params.builder()
.appId("0c498b42")
.apiKey("8579f566eb7f3c4f4a07148ad9e2408c")
.apiSecret("NTRmMmI5MWI4NzIzZTIxN2Q5N2FjMWVl")
.ability(ABILITYID)
.workDir(workPath)
.build()
AiHelper.getInst().init(context, params)
AudioTrackManager.instance?.setSampleRate(AudioTrackManager.sampleRateType.SAMPLE_RATE_16k)
AudioTrackManager.instance?.setListener(completeListener)
}
private val coreListener = CoreListener { type, code ->
when (type) {
ErrType.AUTH -> {
if (code == 0) {
// SDK授权成功
d(TAG, "科大讯飞离线语音合成授权成功!")
} else {
// SDK授权失败授权码为code
d(TAG, "科大讯飞离线语音合成授权失败码:$code")
}
}
else -> {
// SDK状态为type, code
d(TAG, "type:$type, code:$code")
}
}
}
private val completeListener = object : OnCompleteListener {
override fun onComplete() {
onCompleted()
}
}
override fun release() {
if (Thread.currentThread() != Looper.getMainLooper().thread) {
UiThreadHandler.post {
AiHelper.getInst().unInit()
}
} else {
AiHelper.getInst().unInit()
}
}
override fun flush() {
}
override fun speakTTSVoice(tts: String?) {
}
override fun speakTTSVoice(tts: String?, callback: IMogoTTSCallback?) {
}
override fun speakTTSVoice(tts: String?, type: PreemptType?, callback: IMogoTTSCallback?) {
}
override fun speakTTSVoiceWithLevel(tts: String?, level: Int) {
speakTTSVoiceWithLevel(tts, level, null)
}
override fun speakTTSVoiceWithLevel(tts: String?, level: Int, callBack: IMogoTTSCallback?) {
if (tts.isNullOrEmpty()) return
speakMultiLangTTSWithLevel(
MultiLangTtsEntity(
listOf(
LangTtsEntity(
tts,
LanguageType.CHINESE
)
)
), level, callBack
)
}
override fun speakMultiLangTTSWithLevel(
ttsEntity: MultiLangTtsEntity,
level: Int,
callBack: IMogoTTSCallback?
) {
if (Thread.currentThread() != Looper.getMainLooper().thread) {
UiThreadHandler.post {
if (callBack != null) {
speakVoiceMap[ttsEntity.toString()] = callBack
}
speakMultiLangTTSWithLevel(ttsEntity, level)
}
} else {
if (callBack != null) {
speakVoiceMap[ttsEntity.toString()] = callBack
}
speakMultiLangTTSWithLevel(ttsEntity, level)
}
}
override fun stopSpeakTts(text: String?) {
stopTts()
}
override fun stopTts() {
if (Thread.currentThread() != Looper.getMainLooper().thread) {
UiThreadHandler.post {
realStop()
}
} else {
realStop()
}
}
private fun realStop() {
curTtsEntity?.let {
val string = it.toString()
if (speakVoiceMap.containsKey(string)) {
speakVoiceMap.remove(string)?.onStopTts(string)
}
curTtsEntity = null
}
curTtsContent = ""
curTtsLevel = -1
AudioTrackManager.instance?.stopPlay()
}
override fun speakQAndACmd(tts: String?, callback: IMogoTTSCallback?) {
}
override fun speakQAndACmd(
tts: String?,
okWords: Array<out String>?,
cancelWords: Array<out String>?,
callback: IMogoTTSCallback?
) {
}
override fun registerUnWakeupCommand(
cmd: String?,
cmdWords: Array<out String>?,
callback: IMogoTTSCallback?
) {
}
override fun unregisterUnWakeupCommand(cmd: String?) {
}
override fun unregisterUnWakeupCommand(cmd: String?, callback: IMogoTTSCallback?) {
}
override fun registerTtsListener(callback: IGlobalTtsCallback?) {
mGlobalTtsCallback = callback
}
override fun startAIAssist(context: Context?) {
}
override fun startAIAssist(context: Context?, status: Int) {
}
override fun breakOffSpeak() {
}
// 降序插入Tts(目前Level0、1可排队)
private fun insertTts(ttsEntity: MultiLangTtsEntity, level: Int) {
var index = -1
for (i in linkedList.indices.reversed()) {
val nodeLevel = linkedList[i].second
if (linkedList[i].first.isTimeout()) {
linkedList.removeAt(i)
} else if (level > nodeLevel) { // 只有高优先级才插入到前面,等于的情况下是插到后面
index = i
} else { // 再往前元素优先级更大直接break
break
}
}
if (index >= 0) {
linkedList.add(index, Pair(ttsEntity, level))
} else {
linkedList.addLast(Pair(ttsEntity, level))
}
for (ttsPair in linkedList) {
d(TAG, "tts文本为" + ttsPair.first + ",level为" + ttsPair.second)
}
d(TAG, "===================")
}
private fun speakMultiLangTTSWithLevel(ttsEntity: MultiLangTtsEntity, ttsLevel: Int) {
ttsEntity.markTime()
if (ttsLevel == curTtsLevel) {
// 对应p3、p2级别的排队
if (ttsLevel == 0 || ttsLevel == 1) {
d(TAG, "===================")
d(TAG, "插入消息:$ttsEntity,level为$ttsLevel")
insertTts(ttsEntity, ttsLevel)
return
} else if (ttsLevel == 2) {
d(TAG, "已有p2级别在播报新内容直接丢弃!")
return
} else {
// 打断并合成新的
stopTts()
d(TAG, "非Level1同级别打断!")
}
} else {
// 将要TTS的比现在正在TTS的优先级高
if (ttsLevel > curTtsLevel) {
if (curTtsLevel >= 0) {
// 打断并合成高优先级的
stopTts()
}
d(TAG, "高优先级打断低级别的!")
} else {
if (ttsLevel == 0 || ttsLevel == 1) {
d(TAG, "===================")
d(TAG, "插入消息:$ttsEntity,level为$ttsLevel")
insertTts(ttsEntity, ttsLevel)
} else if (ttsLevel == 2) {
d(TAG, "已有高级别在播报,新内容直接丢弃!")
}
return
}
}
curTtsLevel = ttsLevel
curTtsEntity = ttsEntity
// 合成并播放
d(TAG, "tts准备合成$ttsEntity,curTtsLevel为$curTtsLevel")
startSpeak(ttsEntity.ttsNext())
}
private fun startSpeak(langTtsEntity: LangTtsEntity?) {
langTtsEntity?.let {
curTtsContent = it.ttsContent
realSpeak(it.ttsContent, 2)
}
}
/**
* 1:中文, 2:英文, 3:法语, 5:日语, 6:俄语, 9:德语, 15:意大利语, 16:韩语, 23:西班牙语, 12:粤语, 8:印地语, 27:泰语
*/
private fun realSpeak(content: String, language: Int) {
deleteAllOutPutDir()
// 开启会话
val builder = AiInput.builder()
builder.param("vcn", "xiaoyan")
builder.param("language", 1)
builder.param("textEncoding", "UTF-8")
builder.param("pitch", 50)
builder.param("volume", 50)
builder.param("speed", 50)
aiHandle = AiHelper.getInst().start(ABILITYID, builder.build(), null)
if (!aiHandle!!.isSuccess) {
handleErrorEvent("开启会话报错:${aiHandle!!.code}")
aiHandle = null
return
}
// 开始写入
val dataBuilder = AiRequest.Builder()
val input = AiText.get("text").data(content).valid()
dataBuilder.payload(input)
val ret = AiHelper.getInst().write(dataBuilder.build(), aiHandle)
if (ret != 0) {
handleErrorEvent("写能力输入数据失败")
aiHandle = null
}
}
private fun deleteAllOutPutDir() {
val dir = File(OUTPUT_DIR)
if (!dir.exists() || !dir.isDirectory || dir.listFiles() == null) return
for (file in dir.listFiles()!!) {
if (file.isFile) file.delete() // 删除所有文件
}
if (!dir.exists()) {
dir.mkdirs()
}
}
private fun handleCompleteEvent() {
curTtsLevel = -1
if (curTtsEntity != null) {// 多语言
val langTtsEntity = curTtsEntity!!.ttsNext()
if (langTtsEntity != null) {
ttsNextLanguage(langTtsEntity)
} else {
mGlobalTtsCallback?.onTtsSpeakEnd()
speakVoiceMap.remove(curTtsEntity.toString())?.onSpeakEnd(curTtsEntity.toString())
curTtsEntity = null
ttsNextMultiLangEntity()
}
} else {// 单语言
mGlobalTtsCallback?.onTtsSpeakEnd()
speakVoiceMap.remove(curTtsContent)?.onSpeakEnd(curTtsContent)
curTtsContent = ""
ttsNextMultiLangEntity()
}
}
private fun handleErrorEvent(error: String) {
curTtsLevel = -1
if (curTtsEntity != null) {
speakVoiceMap.remove(curTtsEntity.toString())
?.onSpeakError(
curTtsEntity.toString(),
error
)
} else {
speakVoiceMap.remove(curTtsContent)?.onSpeakError(
curTtsContent,
error
)
}
curTtsEntity = null
curTtsContent = ""
}
private val aiRespListener = object : AiListener {
override fun onResult(handleID: Int, list: MutableList<AiResponse>?, usrCxt: Any?) {
if (null != list && list.size > 0) {
val dir = File(OUTPUT_DIR)
var bytes: ByteArray?
for (i in list.indices) {
bytes = list[i].value ?: continue
d(TAG, "onResult:handleID:" + handleID + ":" + list[i].key)
if (!dir.exists()) {
dir.mkdirs()
}
FileUtils.writeFile("${OUTPUT_DIR}/OutPut_mogo.pcm", bytes)
}
}
}
override fun onEvent(
handleID: Int,
event: Int,
eventData: MutableList<AiResponse>?,
usrCxt: Any?
) {
when (event) {
0 -> {
d(TAG, "未知错误")
handleErrorEvent("未知错误")
}
AeeEvent.AEE_EVENT_END.value -> {
aiHandle?.let {
val ret = AiHelper.getInst().end(it)
d(TAG, "AIKit_End$ret")
}
onSpeakBegin()
AudioTrackManager.instance?.startPlay("${OUTPUT_DIR}/OutPut_mogo.pcm")
}
}
}
override fun onError(handleID: Int, err: Int, msg: String?, usrCxt: Any?) {
d(TAG, "错误码:$err,错误信息:$msg")
handleErrorEvent("错误码:$err,错误信息:$msg")
}
}
private fun onSpeakBegin() {
mGlobalTtsCallback?.onTtsSpeakStart()
if (Thread.currentThread() == Looper.getMainLooper().thread) {
curTtsEntity?.let {
speakVoiceMap[it.toString()]?.onSpeakStart(it.toString())
}
} else {
UiThreadHandler.post {
curTtsEntity?.let {
speakVoiceMap[it.toString()]?.onSpeakStart(it.toString())
}
}
}
}
private fun onCompleted() {
if (Thread.currentThread() == Looper.getMainLooper().thread) {
handleCompleteEvent()
} else {
UiThreadHandler.post {
handleCompleteEvent()
}
}
}
/**
* 语音合成下一个MultiLangTtsEntity
*/
private fun ttsNextMultiLangEntity() {
var ttsPair: Pair<MultiLangTtsEntity, Int>?
val ttsList = linkedList
if (!ttsList.isEmpty()) {
ttsPair = ttsList.removeFirst()
while (ttsPair != null && ttsPair.first.isTimeout()) {
if (!ttsList.isEmpty()) {
ttsPair = ttsList.removeFirst()
} else {
ttsPair = null
break
}
}
if (ttsPair != null) {
i(
TAG,
"排队播放的下一条文本为:" + ttsPair.first + ",级别为:" + ttsPair.second
)
speakMultiLangTTSWithLevel(ttsPair.first, ttsPair.second)
} else {
i(TAG, "未超时的队列为空!")
}
} else {
i(TAG, "队列为空!")
}
}
/**
* 语音合成下一个MultiLangTtsEntity中的语言
*/
private fun ttsNextLanguage(langTtsEntity: LangTtsEntity) {
startSpeak(langTtsEntity)
}
}

View File

@@ -0,0 +1,19 @@
package com.mogo.tts.iflytekoffline
class ParamInfo {
var showName: String? = null
var value: String? = null
var value2: Int = 0
constructor(value: String?, showName: String?) {
this.showName = showName
this.value = value
}
constructor(value: Int, showName: String?) {
this.showName = showName
this.value2 = value
}
}

View File

@@ -0,0 +1,37 @@
package com.mogo.tts.iflytekoffline
class XTTSParams {
var vcn: String = "xiaoyan"
var language: Int = 1
var pitch: Int = 50
var speed: Int = 50
var volume: Int = 50
fun getVCN(): List<ParamInfo> {
val vcnList: MutableList<ParamInfo> = ArrayList<ParamInfo>()
vcnList.add(ParamInfo("xiaoyan", "xiaoyan(中文)"))
vcnList.add(ParamInfo("xiaofeng", "xiaofeng(中文)"))
vcnList.add(ParamInfo("catherine", "catherine(英文)"));
// vcnList.add(new ParamInfo("zhongcun", "zhongcun日语"));
// vcnList.add(new ParamInfo("kim", "kim韩语"));
// vcnList.add(new ParamInfo("mariane", "mariane法语"));
// vcnList.add(new ParamInfo("felisa", "felisa西班牙语"));
// vcnList.add(new ParamInfo("keshu", "keshu俄语"));
// vcnList.add(new ParamInfo("christiance", "christiance德语"));
return vcnList
}
fun getLanguage(): List<ParamInfo> {
val languageList: MutableList<ParamInfo> = ArrayList<ParamInfo>()
languageList.add(ParamInfo("1", "中文"))
languageList.add(ParamInfo("1", "中文"))
languageList.add(ParamInfo("2", "英文"));
// languageList.add(new ParamInfo("5", "日语"));
// languageList.add(new ParamInfo("16", "韩语"));
// languageList.add(new ParamInfo("3", "法语"));
// languageList.add(new ParamInfo("23", "西班牙语"));
// languageList.add(new ParamInfo("6", "俄语"));
// languageList.add(new ParamInfo("9", "德语"));
return languageList
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>