[fea]
[唤醒 线程调整]
This commit is contained in:
yangyakun
2025-04-29 14:30:43 +08:00
parent b38fabdb63
commit e8b3e5a7c0
9 changed files with 391 additions and 325 deletions

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/dp_200" android:layout_width="@dimen/dp_200"
android:layout_height="@dimen/dp_580" android:layout_height="@dimen/dp_680"
android:orientation="vertical" android:orientation="vertical"
android:background="@drawable/biz_login_error_info"> android:background="@drawable/biz_login_error_info">

View File

@@ -13,9 +13,9 @@ import com.mogo.eagle.core.function.call.hmi.CallerHmiManager
import com.mogo.eagle.core.function.call.hmi.CallerHmiViewControlListenerManager import com.mogo.eagle.core.function.call.hmi.CallerHmiViewControlListenerManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_TAXI_P import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant.Companion.M_TAXI_P
import com.mogo.eagle.core.utilcode.util.DeviceUtils
import com.mogo.eagle.core.utilcode.util.UiThreadHandler import com.mogo.eagle.core.utilcode.util.UiThreadHandler
import com.mogo.map.listener.IMogoMapListener import com.mogo.map.listener.IMogoMapListener
import com.mogo.mgintelligent.speech.iflytek.WakeUpManager
import com.mogo.och.common.module.biz.provider.CommonService import com.mogo.och.common.module.biz.provider.CommonService
import com.mogo.och.common.module.constant.OchCommonConst import com.mogo.och.common.module.constant.OchCommonConst
import com.mogo.och.common.module.manager.xiaozhi.ZhiStateManager import com.mogo.och.common.module.manager.xiaozhi.ZhiStateManager
@@ -135,6 +135,12 @@ class TaxiPassengerBaseFragment :
settingAndMusicListener() settingAndMusicListener()
bottom.setOverMapApplyClick(object : BottomBar.ApplyClickLintener{ bottom.setOverMapApplyClick(object : BottomBar.ApplyClickLintener{
override fun onApplyClick(selectItem: BottomBar.SelectView) { override fun onApplyClick(selectItem: BottomBar.SelectView) {
CallerLogger.d(TAG,"选择的项:${selectItem}")
if(selectItem==BottomBar.SelectView.PRECISIONMAP){
WakeUpManager.startWakeUp()
}else{
WakeUpManager.stopWakeup()
}
when (selectItem) { when (selectItem) {
BottomBar.SelectView.PRECISIONMAP -> { BottomBar.SelectView.PRECISIONMAP -> {
CallerHmiViewControlListenerManager.invokeMainPageViewVisible(View.VISIBLE) CallerHmiViewControlListenerManager.invokeMainPageViewVisible(View.VISIBLE)

View File

@@ -11,8 +11,8 @@ import com.mogo.mgintelligent.speech.AsrResult
import com.mogo.mgintelligent.speech.AsrState import com.mogo.mgintelligent.speech.AsrState
import com.mogo.mgintelligent.speech.IWakeUpListener import com.mogo.mgintelligent.speech.IWakeUpListener
import com.mogo.mgintelligent.speech.MGSpeech import com.mogo.mgintelligent.speech.MGSpeech
import com.mogo.mgintelligent.speech.iflytek.WakeManager
import com.mogo.mgintelligent.speech.iflytek.WakeUpListener import com.mogo.mgintelligent.speech.iflytek.WakeUpListener
import com.mogo.mgintelligent.speech.iflytek.WakeUpManager
import com.mogo.och.bridge.autopilot.location.OchLocationManager import com.mogo.och.bridge.autopilot.location.OchLocationManager
import com.mogo.och.data.taxi.BaseOrderBean import com.mogo.och.data.taxi.BaseOrderBean
import com.mogo.och.data.taxi.TaxiOrderStatusEnum import com.mogo.och.data.taxi.TaxiOrderStatusEnum
@@ -86,7 +86,7 @@ class AIViewModel : ViewModel(), AIMessageManager.AIMessageListener, IWakeUpList
private val wakeUpListener = object :WakeUpListener{ private val wakeUpListener = object :WakeUpListener{
override fun wakeupSuccess() { override fun wakeupSuccess() {
onWakeUp() onWakeUp()
WakeManager.stopWakeup() WakeUpManager.stopWakeup()
} }
} }
@@ -99,7 +99,7 @@ class AIViewModel : ViewModel(), AIMessageManager.AIMessageListener, IWakeUpList
OrderModel.setOrderStatusCallback(TAG,orderListener) OrderModel.setOrderStatusCallback(TAG,orderListener)
OchLocationManager.addGCJ02Listener(TAG,1,locationCallback) OchLocationManager.addGCJ02Listener(TAG,1,locationCallback)
AIMessageManager.registerListener(this) AIMessageManager.registerListener(this)
WakeManager.setWakeUpListener(wakeUpListener) WakeUpManager.setWakeUpListener(wakeUpListener)
mgSpeech.weakUpListener = this mgSpeech.weakUpListener = this
mgSpeech.startWeakUp() mgSpeech.startWeakUp()
@@ -134,7 +134,7 @@ class AIViewModel : ViewModel(), AIMessageManager.AIMessageListener, IWakeUpList
override fun onCleared() { override fun onCleared() {
AIMessageManager.unregisterListener(this) AIMessageManager.unregisterListener(this)
mgSpeech.weakUpListener = null mgSpeech.weakUpListener = null
WakeManager.setWakeUpListener(null) WakeUpManager.setWakeUpListener(null)
mgSpeech.stopWeakUp() mgSpeech.stopWeakUp()
llmResultJob?.cancel() llmResultJob?.cancel()
OrderModel.setOrderStatusCallback(TAG,null) OrderModel.setOrderStatusCallback(TAG,null)
@@ -240,7 +240,7 @@ class AIViewModel : ViewModel(), AIMessageManager.AIMessageListener, IWakeUpList
} }
AsrState.STATE_EXIT -> { AsrState.STATE_EXIT -> {
WakeManager.startWakeUp() WakeUpManager.startWakeUp()
_asrUIStateFlow.value = AsrUIState.Idle _asrUIStateFlow.value = AsrUIState.Idle
mgSpeech.speak(tipsExit) mgSpeech.speak(tipsExit)
mgSpeech.isAssistantShow(false) mgSpeech.isAssistantShow(false)

View File

@@ -15,15 +15,10 @@ import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.lifecycle.findViewTreeViewModelStoreOwner
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.mogo.eagle.core.data.autopilot.pnc.PncActionsHelper
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotPlanningActionsListener
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener.Companion.STATUS_AUTOPILOT_RUNNING
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager
import com.mogo.eagle.core.function.call.autopilot.CallerPlanningActionsListenerManager import com.mogo.eagle.core.function.call.autopilot.CallerPlanningActionsListenerManager
import com.mogo.eagle.core.utilcode.kotlin.onClick import com.mogo.eagle.core.utilcode.kotlin.onClick
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.util.UiThreadHandler import com.mogo.mgintelligent.speech.iflytek.WakeUpManager
import com.mogo.mgintelligent.speech.iflytek.WakeManager
import com.mogo.och.common.module.manager.logchainanalytic.OchChainLogManager import com.mogo.och.common.module.manager.logchainanalytic.OchChainLogManager
import com.mogo.och.common.module.utils.BigFrameAnimatorContainer import com.mogo.och.common.module.utils.BigFrameAnimatorContainer
import com.mogo.och.common.module.utils.RxUtils import com.mogo.och.common.module.utils.RxUtils
@@ -42,8 +37,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import mogo.telematics.pad.MessagePad
import mogo.telematics.pad.MessagePad.DrivingState.USING_RSI_LINK_VALUE
class AiView @JvmOverloads constructor( class AiView @JvmOverloads constructor(
context: Context, context: Context,
@@ -170,11 +163,6 @@ class AiView @JvmOverloads constructor(
super.onVisibilityAggregated(isVisible) super.onVisibilityAggregated(isVisible)
CallerLogger.d(TAG,"是否展示中:${isVisible}") CallerLogger.d(TAG,"是否展示中:${isVisible}")
try { try {
if(isVisible){
WakeManager.startWakeUp()
}else{
WakeManager.stopWakeup()
}
if(isVisible){ if(isVisible){
aiAnimator?.start() aiAnimator?.start()
RxUtils.createSubscribe(3_000) { RxUtils.createSubscribe(3_000) {
@@ -202,6 +190,7 @@ class AiView @JvmOverloads constructor(
) { ) {
CallerLogger.d(TAG,"${tName()} onTransitionCompleted:${motionLayout?.id}_$currentId") CallerLogger.d(TAG,"${tName()} onTransitionCompleted:${motionLayout?.id}_$currentId")
rvMessagesEmpty.visibility = View.VISIBLE rvMessagesEmpty.visibility = View.VISIBLE
WakeUpManager.startWakeUp()
startContextInfo() startContextInfo()
startListInfo() startListInfo()
} }

View File

@@ -62,12 +62,6 @@ class StatusBarView @JvmOverloads constructor(
LayoutInflater.from(context).inflate(R.layout.taxt_u_p_statusview, this, true) LayoutInflater.from(context).inflate(R.layout.taxt_u_p_statusview, this, true)
isClickable = true isClickable = true
isFocusable = true isFocusable = true
textClockDate.onClick {
WakeManager.startWakeUp()
}
bcsv_status.onClick {
WakeManager.stopWakeup()
}
} }
override fun onAttachedToWindow() { override fun onAttachedToWindow() {

View File

@@ -1,4 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.mogo.mgintelligent.speech" > package="com.mogo.mgintelligent.speech" >
<uses-sdk tools:overrideLibrary="com.bun.miitmdid" />
</manifest> </manifest>

View File

@@ -1,63 +1,23 @@
package com.mogo.mgintelligent.speech.iflytek package com.mogo.mgintelligent.speech.iflytek
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.pm.PackageManager
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.util.Log import android.util.Log
import androidx.core.app.ActivityCompat
import com.iflytek.aikit.core.AiAudio
import com.iflytek.aikit.core.AiHandle
import com.iflytek.aikit.core.AiHelper import com.iflytek.aikit.core.AiHelper
import com.iflytek.aikit.core.AiListener
import com.iflytek.aikit.core.AiRequest
import com.iflytek.aikit.core.AiResponse
import com.iflytek.aikit.core.AiStatus
import com.iflytek.aikit.core.BaseLibrary import com.iflytek.aikit.core.BaseLibrary
import com.iflytek.aikit.core.CoreListener import com.iflytek.aikit.core.CoreListener
import com.iflytek.aikit.core.ErrType import com.iflytek.aikit.core.ErrType
import com.iflytek.aikit.core.LogLvl import com.iflytek.aikit.core.LogLvl
import com.mogo.commons.AbsMogoApplication import com.mogo.commons.AbsMogoApplication
import com.mogo.eagle.core.function.call.setting.CallerRequestActivityHandleManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.util.FileUtils import com.mogo.eagle.core.utilcode.util.FileUtils
import com.mogo.eagle.core.utilcode.util.ResourceUtils import com.mogo.eagle.core.utilcode.util.ResourceUtils
import com.mogo.eagle.core.utilcode.util.ThreadUtils import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.mogo.eagle.core.utilcode.util.Utils import com.mogo.eagle.core.utilcode.util.Utils
import java.io.BufferedWriter
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStreamWriter
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.abs
import kotlin.math.log10
object WakeManager { object WakeManager {
private val TAG = "WakeManager" private const val TAG = "WakeManager"
private const val ABILITYID: String = "e867a88f2"
private val weakupKey = "你好小智,小智小智,小智你好"
private val isEnd = AtomicBoolean(true)
//录音是否在进行
private val isRecording = AtomicBoolean(false)
private var aiHandle: AiHandle? = null
private var audioRecord: AudioRecord? = null
private var _wakeUpListener:WakeUpListener?=null
//录音缓冲区大小
private const val BUFFER_SIZE = 1280
private val coreListener = CoreListener { type, code -> private val coreListener = CoreListener { type, code ->
Log.i(TAG, "core listener code:$code") Log.i(TAG, "core listener code:$code")
@@ -66,10 +26,10 @@ object WakeManager {
ErrType.AUTH -> { ErrType.AUTH -> {
if (code == 0) { if (code == 0) {
Log.i(TAG, "SDK授权成功") Log.i(TAG, "SDK授权成功")
WakeUpManager.startThread()
} else { } else {
Log.i(TAG, "SDK授权失败授权码为:$code") Log.i(TAG, "SDK授权失败授权码为:$code")
} }
} }
ErrType.HTTP -> Log.i(TAG, "SDK状态HTTP认证结果$code") ErrType.HTTP -> Log.i(TAG, "SDK状态HTTP认证结果$code")
@@ -79,258 +39,6 @@ object WakeManager {
} }
fun setWakeUpListener(wakeUpListener:WakeUpListener?){
this._wakeUpListener = wakeUpListener
}
fun startWakeUp(){
ThreadUtils.getIoPool().submit {
// 检测权限
val granted = ActivityCompat.checkSelfPermission(AbsMogoApplication.getApp(),
Manifest.permission.RECORD_AUDIO)
if(granted != PackageManager. PERMISSION_GRANTED){
CallerRequestActivityHandleManager.requestPermission(TAG,Manifest.permission.RECORD_AUDIO)
return@submit
}
AiHelper.getInst().registerListener(ABILITYID, edListener)
CallerLogger.d(TAG,"START")
val ret = start()
if (ret == 0) {
createAudioRecord() //创建录音器
isRecording.set(true)
audioRecord?.startRecording() //录音器启动录音
writeRecording(AiStatus.BEGIN)
}
}
}
fun stopWakeup(){
if(!isEnd.get()) {
writeRecording(AiStatus.END)
}
}
/**
* 能力监听回调
*/
private val edListener: AiListener = object : AiListener {
override fun onResult(handleID: Int, outputData: List<AiResponse>, usrContext: Any?) {
if (null != outputData && outputData.size > 0) {
for (i in outputData.indices) {
CallerLogger.d(TAG, "onResult:handleID:" + handleID + ":" + outputData[i].key)
val key = outputData[i].key //引擎结果的key
val bytes = outputData[i].value //识别结果
val result = String(bytes)
CallerLogger.d(TAG, "key=$key")
CallerLogger.d(TAG, "value=$result")
CallerLogger.d(TAG, "status=" + outputData[i].status)
if ((key == "func_wake_up" || key == "func_pre_wakeup")) {
CallerLogger.d(TAG,"$key: \n $result")
}
_wakeUpListener?.wakeupSuccess()
}
}
}
override fun onEvent(i: Int, i1: Int, list: List<AiResponse>, o: Any) {
CallerLogger.i(TAG, "onEvent:$i,event:$i1")
}
override fun onError(i: Int, i1: Int, s: String, o: Any) {
CallerLogger.d(TAG,"错误通知,能力执行终止,Ability $i ERROR::$s,err code:$i1")
}
}
private fun writeRecording(aiStatus:AiStatus){
/**
* 写入数据-录音方式
*/
val data = ByteArray(BUFFER_SIZE)
val read = audioRecord!!.read(data, 0, BUFFER_SIZE)
//平方和除以数据总长度,得到音量大小。
val volume = calculateVolume(data)
// CallerLogger.d(TAG,"当前分贝:${ abs(volume.toDouble())}")
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
//处理录音数据
write(data, aiStatus) //送尾帧
}
if (AiStatus.END == aiStatus) {
if ( audioRecord != null) {
audioRecord!!.stop()
isRecording.set(false)
end()
}
} else {
if (isRecording.get()) {
writeRecording(AiStatus.CONTINUE) //调用write方法送音频数据给引擎
}
}
}
/**
* 结束会话
*/
private fun end() {
if (!isEnd.get()) {
val ret = AiHelper.getInst().end(aiHandle)
if (ret == 0) {
isEnd.set(true)
aiHandle = null
CallerLogger.d(TAG,"唤醒完成end $ret")
} else {
isEnd.set(false)
ThreadUtils.runOnUiThreadDelayed({
end()
CallerLogger.d(TAG,"关闭失败2s后重新广播唤醒完成end $ret")
},2_000)
CallerLogger.d(TAG,"唤醒完成end $ret")
}
}
}
/**
* 写入数据
*/
private fun write(part: ByteArray, status: AiStatus) {
if (isEnd.get()) {
return
}
val dataBuilder = AiRequest.builder()
var ret = 0
/**
* 送入音频需要标识音频的状态第一帧为起始帧status要传AiStatus.BEGIN,最后一帧为结束帧status要传AiStatus.END,其他为中间帧status要传AiStatus.CONTINUE
* 音频要求16bit16K单声道的pcm音频。
* 建议每次发送音频间隔40ms每次发送音频字节数为一帧音频大小的整数倍。
*/
val aiAudio = AiAudio.get("wav").data(part).status(status).valid()
dataBuilder.payload(aiAudio)
ret = AiHelper.getInst().write(dataBuilder.build(), aiHandle)
if (ret != 0) {
CallerLogger.d(TAG,"write失败$ret ")
}
}
/**
* 根据录音数据计算音量
*/
private fun calculateVolume(buffer: ByteArray): Int {
var sumVolume = 0.0
var avgVolume = 0.0
var volume = 0
var i = 0
while (i < buffer.size) {
val v1 = buffer[i].toInt() and 0xFF
val v2 = buffer[i + 1].toInt() and 0xFF
var temp = v1 + (v2 shl 8) // 小端
if (temp >= 0x8000) {
temp = 0xffff - temp
}
sumVolume += abs(temp.toDouble())
i += 2
}
avgVolume = sumVolume / buffer.size / 2
volume = (log10(1 + avgVolume) * 10).toInt()
return volume
}
/**
* 创建录音器
*/
@SuppressLint("MissingPermission")
private fun createAudioRecord() {
if (isRecording.get()) {
return
}
if (audioRecord == null) {
Log.d(TAG, "createAudioRecord")
audioRecord = AudioRecord(
MediaRecorder.AudioSource.MIC,
16000,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
BUFFER_SIZE
)
}
}
/**
* 开始会话
*/
private fun start(): Int {
if (!keyword2File()) {
CallerLogger.d(TAG,"唤醒词文件写入失败,请检查是否有读写权限。")
return -1
}
val customBuilder = AiRequest.builder()
customBuilder.customText("key_word", getIvwDir() + "/keyword.txt", 0)
var ret = AiHelper.getInst().loadData(ABILITYID, customBuilder.build())
if (ret != 0) {
CallerLogger.d(TAG,"open ivw loadData 失败:$ret")
return ret
}
CallerLogger.d(TAG,"open ivw loadData success$ret")
val indexs = intArrayOf(0)
ret = AiHelper.getInst().specifyDataSet(ABILITYID, "key_word", indexs) //从缓存中把个性化资源设置到引擎中
if (ret != 0) {
CallerLogger.d(TAG,"open ivw specifyDataSet 失败:$ret")
return ret
}
CallerLogger.d(TAG,"open ivw specifyDataSet success$ret")
val paramBuilder = AiRequest.builder()
paramBuilder.param("wdec_param_nCmThreshold", "0 0:800")
paramBuilder.param("gramLoad", true)
isEnd.set(false)
aiHandle = AiHelper.getInst().start(ABILITYID, paramBuilder.build(), null)
if (aiHandle?.code != 0) {
CallerLogger.d(TAG,"open ivw start失败${aiHandle?.getCode()}")
return aiHandle?.code ?:0
}
return 0
}
private fun keyword2File(): Boolean {
try {
val keywordFile: File = File(getIvwDir() + "/keyword.txt")
if (keywordFile.exists()) {
//强制清空内容
keywordFile.delete()
}
val binFile: File = File(getIvwDir() + "/keyword.bin")
if (binFile.exists()) {
binFile.delete()
}
var temp: String =weakupKey
if (temp.isEmpty()) {
temp = "你好小智"
}
val str = temp.replace("", ",")
val keywords = str.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (!keywordFile.exists()) {
keywordFile.createNewFile()
}
val writer = OutputStreamWriter(
FileOutputStream(keywordFile),
"UTF-8"
)
val bufferedWriter = BufferedWriter(writer)
for (i in keywords.indices) {
bufferedWriter.write(keywords[i])
bufferedWriter.write(";")
bufferedWriter.newLine() //写入换行
}
bufferedWriter.close()
} catch (e: IOException) {
e.printStackTrace()
return false
}
return true
}
fun initWakeAi(context: Context) { fun initWakeAi(context: Context) {
@@ -342,6 +50,7 @@ object WakeManager {
file.mkdirs() file.mkdirs()
} }
AiHelper.getInst().setLogInfo(LogLvl.VERBOSE, 1, "${workDir}aeeLog.txt") AiHelper.getInst().setLogInfo(LogLvl.VERBOSE, 1, "${workDir}aeeLog.txt")
AiHelper.getInst().setLogMode(2)
//设定初始化参数 //设定初始化参数
val params = BaseLibrary.Params.builder() val params = BaseLibrary.Params.builder()
@@ -377,7 +86,7 @@ object WakeManager {
val assets = Utils.getApp().assets.list(dir)?.toMutableList() val assets = Utils.getApp().assets.list(dir)?.toMutableList()
val tartgetFile = File(targetDir) val tartgetFile = File(targetDir)
if(tartgetFile.isDirectory){ if(tartgetFile.isDirectory){
tartgetFile.listFiles { dir, name -> tartgetFile.listFiles { _, name ->
(assets?.contains(name)?:true).apply { (assets?.contains(name)?:true).apply {
assets?.remove(name) assets?.remove(name)
} }
@@ -393,8 +102,4 @@ object WakeManager {
} }
throw IllegalArgumentException("找不到 Application") throw IllegalArgumentException("找不到 Application")
} }
fun getIvwDir(): String {
return getWorkDir()+File.separator+"ivw"
}
} }

View File

@@ -0,0 +1,369 @@
package com.mogo.mgintelligent.speech.iflytek
import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import androidx.core.app.ActivityCompat
import com.iflytek.aikit.core.AiAudio
import com.iflytek.aikit.core.AiHandle
import com.iflytek.aikit.core.AiHelper
import com.iflytek.aikit.core.AiListener
import com.iflytek.aikit.core.AiRequest
import com.iflytek.aikit.core.AiResponse
import com.iflytek.aikit.core.AiStatus
import com.mogo.commons.AbsMogoApplication
import com.mogo.eagle.core.function.call.setting.CallerRequestActivityHandleManager
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import java.io.BufferedWriter
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStreamWriter
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.abs
import kotlin.math.log10
object WakeUpManager {
private const val START: Int = 0x0001
private const val WRITE_BY_RECORDING: Int = 0x0002
private const val END: Int = 0x0004
//录音缓冲区大小
private const val BUFFER_SIZE = 1280
private const val ABILITYID: String = "e867a88f2"
private const val weakupKey = "你好小智,小智小智,小智你好"
private val isEnd = AtomicBoolean(true)
//录音是否在进行
private val isRecording = AtomicBoolean(false)
private var aiHandle: AiHandle? = null
private const val TAG = "WakeUpManager"
private var mHandler: Handler? = null
private var audioRecord: AudioRecord? = null
private val mThread = Thread {
Looper.prepare()
mHandler = object : Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
START -> {
/**
* 开始会话
*/
Log.d(TAG, "START")
val ret: Int = start()
if (ret == 0) {
val type = msg.arg1
if (type == WRITE_BY_RECORDING) {
createAudioRecord() //创建录音器
isRecording.set(true)
audioRecord?.startRecording() //录音器启动录音
}
mHandler?.removeCallbacksAndMessages(null) //清空消息队列
val writeMsg = Message()
writeMsg.what = type
writeMsg.obj = AiStatus.BEGIN
mHandler?.sendMessage(writeMsg) //调用write方法送音频数据给引擎
}
}
WRITE_BY_RECORDING -> {
/**
* 写入数据-录音方式
*/
val status = msg.obj as AiStatus
val data = ByteArray(BUFFER_SIZE)
val read: Int = audioRecord?.read(data, 0, BUFFER_SIZE)?:AudioRecord.ERROR_INVALID_OPERATION
//平方和除以数据总长度,得到音量大小。
val volume: Int = calculateVolume(data)
//CallerLogger.d(TAG,"当前分贝:${ abs(volume.toDouble())}")
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
//处理录音数据
write(data, status) //送尾帧
}
if (AiStatus.END == status) {
if (audioRecord != null) {
audioRecord?.stop()
isRecording.set(false)
mHandler?.sendEmptyMessage(END)
}
} else {
if (isRecording.get()) {
val writeMsg = Message()
writeMsg.what = WRITE_BY_RECORDING
writeMsg.obj = AiStatus.CONTINUE
mHandler?.sendMessage(writeMsg) //调用write方法送音频数据给引擎
}
}
}
END -> {
/**
* 结束会话
*/
Log.d(TAG, "END")
end()
}
}
}
}
Looper.loop()
}
/**
* 能力监听回调
*/
private val edListener: AiListener = object : AiListener {
override fun onResult(handleID: Int, outputData: List<AiResponse>, usrContext: Any?) {
if (outputData.isNotEmpty()) {
for (i in outputData.indices) {
CallerLogger.d(TAG, "onResult:handleID:" + handleID + ":" + outputData[i].key)
val key = outputData[i].key //引擎结果的key
val bytes = outputData[i].value //识别结果
val result = String(bytes)
CallerLogger.d(TAG, "key=$key")
CallerLogger.d(TAG, "value=$result")
CallerLogger.d(TAG, "status=" + outputData[i].status)
if ((key == "func_wake_up" || key == "func_pre_wakeup")) {
CallerLogger.d(TAG,"$key: \n $result")
}
_wakeUpListener?.wakeupSuccess()
}
}
}
override fun onEvent(i: Int, i1: Int, list: List<AiResponse>, o: Any) {
CallerLogger.i(TAG, "onEvent:$i,event:$i1")
}
override fun onError(i: Int, i1: Int, s: String, o: Any) {
CallerLogger.d(TAG,"错误通知,能力执行终止,Ability $i ERROR::$s,err code:$i1")
}
}
private var _wakeUpListener:WakeUpListener?=null
fun setWakeUpListener(wakeUpListener:WakeUpListener?){
this._wakeUpListener = wakeUpListener
}
fun startThread(){
CallerLogger.d(TAG,"startThread")
mThread.start()
}
fun startWakeUp() {
if (isRecording.get() || !isEnd.get()) {
return
}
CallerLogger.d(TAG,"startWakeUp")
ThreadUtils.getIoPool().submit {
// 检测权限
val granted = ActivityCompat.checkSelfPermission(
AbsMogoApplication.getApp(),
Manifest.permission.RECORD_AUDIO)
if(granted != PackageManager. PERMISSION_GRANTED){
CallerRequestActivityHandleManager.requestPermission(TAG, Manifest.permission.RECORD_AUDIO)
return@submit
}
AiHelper.getInst().registerListener(ABILITYID, edListener)
val msg = Message()
msg.what = START
msg.arg1 = WRITE_BY_RECORDING
mHandler!!.sendMessage(msg)
}
}
fun stopWakeup(){
CallerLogger.d(TAG,"stopWakeup")
if(!isEnd.get()) {
val msg = Message()
msg.what = WRITE_BY_RECORDING
msg.obj = AiStatus.END
mHandler!!.sendMessage(msg)
}
}
/**
* 写入数据
*/
private fun write(part: ByteArray, status: AiStatus) {
if (isEnd.get()) {
return
}
val dataBuilder = AiRequest.builder()
/**
* 送入音频需要标识音频的状态第一帧为起始帧status要传AiStatus.BEGIN,最后一帧为结束帧status要传AiStatus.END,其他为中间帧status要传AiStatus.CONTINUE
* 音频要求16bit16K单声道的pcm音频。
* 建议每次发送音频间隔40ms每次发送音频字节数为一帧音频大小的整数倍。
*/
val aiAudio = AiAudio.get("wav").data(part).status(status).valid()
dataBuilder.payload(aiAudio)
val ret: Int = AiHelper.getInst().write(dataBuilder.build(), aiHandle)
if (ret != 0) {
CallerLogger.d(TAG,"write失败$ret ")
}
}
/**
* 创建录音器
*/
@SuppressLint("MissingPermission")
private fun createAudioRecord() {
if (isRecording.get()) {
return
}
if (audioRecord == null) {
Log.d(TAG, "createAudioRecord")
audioRecord = AudioRecord(
MediaRecorder.AudioSource.MIC,
16000,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
BUFFER_SIZE
)
}
}
/**
* 根据录音数据计算音量
*/
private fun calculateVolume(buffer: ByteArray): Int {
var sumVolume = 0.0
val volume: Int
var i = 0
while (i < buffer.size) {
val v1 = buffer[i].toInt() and 0xFF
val v2 = buffer[i + 1].toInt() and 0xFF
var temp = v1 + (v2 shl 8) // 小端
if (temp >= 0x8000) {
temp = 0xffff - temp
}
sumVolume += abs(temp.toDouble())
i += 2
}
val avgVolume: Double = sumVolume / buffer.size / 2
volume = (log10(1 + avgVolume) * 10).toInt()
return volume
}
/**
* 开始会话
*/
private fun start(): Int {
if (!keyword2File()) {
CallerLogger.d(TAG,"唤醒词文件写入失败,请检查是否有读写权限。")
return -1
}
val customBuilder = AiRequest.builder()
customBuilder.customText("key_word", getIvwDir() + "/keyword.txt", 0)
var ret = AiHelper.getInst().loadData(ABILITYID, customBuilder.build())
if (ret != 0) {
CallerLogger.d(TAG,"open ivw loadData 失败:$ret")
return ret
}
CallerLogger.d(TAG,"open ivw loadData success$ret")
val indexs = intArrayOf(0)
ret = AiHelper.getInst().specifyDataSet(ABILITYID, "key_word", indexs) //从缓存中把个性化资源设置到引擎中
if (ret != 0) {
CallerLogger.d(TAG,"open ivw specifyDataSet 失败:$ret")
return ret
}
CallerLogger.d(TAG,"open ivw specifyDataSet success$ret")
val paramBuilder = AiRequest.builder()
paramBuilder.param("wdec_param_nCmThreshold", "0 0:800")
paramBuilder.param("gramLoad", true)
isEnd.set(false)
aiHandle = AiHelper.getInst().start(ABILITYID, paramBuilder.build(), null)
if (aiHandle?.code != 0) {
CallerLogger.d(TAG,"open ivw start失败${aiHandle?.getCode()}")
return aiHandle?.code ?:0
}
return 0
}
/**
* 结束会话
*/
private fun end() {
if (!isEnd.get()) {
val ret = AiHelper.getInst().end(aiHandle)
if (ret == 0) {
isEnd.set(true)
aiHandle = null
CallerLogger.d(TAG,"唤醒完成end $ret")
} else {
isEnd.set(false)
ThreadUtils.runOnUiThreadDelayed({
end()
CallerLogger.d(TAG,"关闭失败2s后重新广播唤醒完成end $ret")
},2_000)
CallerLogger.d(TAG,"唤醒完成end $ret")
}
}
}
private fun keyword2File(): Boolean {
try {
val keywordFile = File(getIvwDir() + "/keyword.txt")
if (keywordFile.exists()) {
//强制清空内容
keywordFile.delete()
}
val binFile = File(getIvwDir() + "/keyword.bin")
if (binFile.exists()) {
binFile.delete()
}
var temp: String = weakupKey
if (temp.isEmpty()) {
temp = "你好小智"
}
val str = temp.replace("", ",")
val keywords = str.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (!keywordFile.exists()) {
keywordFile.createNewFile()
}
val writer = OutputStreamWriter(
FileOutputStream(keywordFile),
"UTF-8"
)
val bufferedWriter = BufferedWriter(writer)
for (i in keywords.indices) {
bufferedWriter.write(keywords[i])
bufferedWriter.write(";")
bufferedWriter.newLine() //写入换行
}
bufferedWriter.close()
} catch (e: IOException) {
e.printStackTrace()
return false
}
return true
}
private fun getIvwDir(): String {
return WakeManager.getWorkDir() +File.separator+"ivw"
}
}