diff --git a/OCH/common/biz/src/main/res/layout/biz_login_business_view.xml b/OCH/common/biz/src/main/res/layout/biz_login_business_view.xml index 987e471991..5e7f8690bb 100644 --- a/OCH/common/biz/src/main/res/layout/biz_login_business_view.xml +++ b/OCH/common/biz/src/main/res/layout/biz_login_business_view.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="@dimen/dp_200" - android:layout_height="@dimen/dp_580" + android:layout_height="@dimen/dp_680" android:orientation="vertical" android:background="@drawable/biz_login_error_info"> diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/TaxiPassengerBaseFragment.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/TaxiPassengerBaseFragment.kt index 1545626b05..20898c27ae 100644 --- a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/TaxiPassengerBaseFragment.kt +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/TaxiPassengerBaseFragment.kt @@ -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.utilcode.mogo.logger.CallerLogger 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.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.constant.OchCommonConst import com.mogo.och.common.module.manager.xiaozhi.ZhiStateManager @@ -135,6 +135,12 @@ class TaxiPassengerBaseFragment : settingAndMusicListener() bottom.setOverMapApplyClick(object : BottomBar.ApplyClickLintener{ override fun onApplyClick(selectItem: BottomBar.SelectView) { + CallerLogger.d(TAG,"选择的项:${selectItem}") + if(selectItem==BottomBar.SelectView.PRECISIONMAP){ + WakeUpManager.startWakeUp() + }else{ + WakeUpManager.stopWakeup() + } when (selectItem) { BottomBar.SelectView.PRECISIONMAP -> { CallerHmiViewControlListenerManager.invokeMainPageViewVisible(View.VISIBLE) diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AIViewModel.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AIViewModel.kt index ee71cbe84c..5644067e07 100644 --- a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AIViewModel.kt +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AIViewModel.kt @@ -11,8 +11,8 @@ import com.mogo.mgintelligent.speech.AsrResult import com.mogo.mgintelligent.speech.AsrState import com.mogo.mgintelligent.speech.IWakeUpListener 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.WakeUpManager import com.mogo.och.bridge.autopilot.location.OchLocationManager import com.mogo.och.data.taxi.BaseOrderBean import com.mogo.och.data.taxi.TaxiOrderStatusEnum @@ -86,7 +86,7 @@ class AIViewModel : ViewModel(), AIMessageManager.AIMessageListener, IWakeUpList private val wakeUpListener = object :WakeUpListener{ override fun wakeupSuccess() { onWakeUp() - WakeManager.stopWakeup() + WakeUpManager.stopWakeup() } } @@ -99,7 +99,7 @@ class AIViewModel : ViewModel(), AIMessageManager.AIMessageListener, IWakeUpList OrderModel.setOrderStatusCallback(TAG,orderListener) OchLocationManager.addGCJ02Listener(TAG,1,locationCallback) AIMessageManager.registerListener(this) - WakeManager.setWakeUpListener(wakeUpListener) + WakeUpManager.setWakeUpListener(wakeUpListener) mgSpeech.weakUpListener = this mgSpeech.startWeakUp() @@ -134,7 +134,7 @@ class AIViewModel : ViewModel(), AIMessageManager.AIMessageListener, IWakeUpList override fun onCleared() { AIMessageManager.unregisterListener(this) mgSpeech.weakUpListener = null - WakeManager.setWakeUpListener(null) + WakeUpManager.setWakeUpListener(null) mgSpeech.stopWeakUp() llmResultJob?.cancel() OrderModel.setOrderStatusCallback(TAG,null) @@ -240,7 +240,7 @@ class AIViewModel : ViewModel(), AIMessageManager.AIMessageListener, IWakeUpList } AsrState.STATE_EXIT -> { - WakeManager.startWakeUp() + WakeUpManager.startWakeUp() _asrUIStateFlow.value = AsrUIState.Idle mgSpeech.speak(tipsExit) mgSpeech.isAssistantShow(false) diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AiView.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AiView.kt index ba271fbc90..12620386eb 100644 --- a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AiView.kt +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/aiview/AiView.kt @@ -15,15 +15,10 @@ import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.recyclerview.widget.LinearLayoutManager 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.utilcode.kotlin.onClick import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger -import com.mogo.eagle.core.utilcode.util.UiThreadHandler -import com.mogo.mgintelligent.speech.iflytek.WakeManager +import com.mogo.mgintelligent.speech.iflytek.WakeUpManager import com.mogo.och.common.module.manager.logchainanalytic.OchChainLogManager import com.mogo.och.common.module.utils.BigFrameAnimatorContainer import com.mogo.och.common.module.utils.RxUtils @@ -42,8 +37,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine -import mogo.telematics.pad.MessagePad -import mogo.telematics.pad.MessagePad.DrivingState.USING_RSI_LINK_VALUE class AiView @JvmOverloads constructor( context: Context, @@ -170,11 +163,6 @@ class AiView @JvmOverloads constructor( super.onVisibilityAggregated(isVisible) CallerLogger.d(TAG,"是否展示中:${isVisible}") try { - if(isVisible){ - WakeManager.startWakeUp() - }else{ - WakeManager.stopWakeup() - } if(isVisible){ aiAnimator?.start() RxUtils.createSubscribe(3_000) { @@ -202,6 +190,7 @@ class AiView @JvmOverloads constructor( ) { CallerLogger.d(TAG,"${tName()} onTransitionCompleted:${motionLayout?.id}_$currentId") rvMessagesEmpty.visibility = View.VISIBLE + WakeUpManager.startWakeUp() startContextInfo() startListInfo() } diff --git a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/statusview/StatusBarView.kt b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/statusview/StatusBarView.kt index d4960b3e42..13f5dbd5a3 100644 --- a/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/statusview/StatusBarView.kt +++ b/OCH/taxi/unmanned-passenger/src/main/java/com/mogo/och/unmanned/passenger/ui/statusview/StatusBarView.kt @@ -62,12 +62,6 @@ class StatusBarView @JvmOverloads constructor( LayoutInflater.from(context).inflate(R.layout.taxt_u_p_statusview, this, true) isClickable = true isFocusable = true - textClockDate.onClick { - WakeManager.startWakeUp() - } - bcsv_status.onClick { - WakeManager.stopWakeup() - } } override fun onAttachedToWindow() { diff --git a/libraries/mogo-speech/src/main/AndroidManifest.xml b/libraries/mogo-speech/src/main/AndroidManifest.xml index a253f68ad2..2b0ee014cc 100644 --- a/libraries/mogo-speech/src/main/AndroidManifest.xml +++ b/libraries/mogo-speech/src/main/AndroidManifest.xml @@ -1,4 +1,7 @@ + + \ No newline at end of file diff --git a/libraries/mogo-speech/src/main/assets/ivw/version_2 b/libraries/mogo-speech/src/main/assets/ivw/version_1 similarity index 100% rename from libraries/mogo-speech/src/main/assets/ivw/version_2 rename to libraries/mogo-speech/src/main/assets/ivw/version_1 diff --git a/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/iflytek/WakeManager.kt b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/iflytek/WakeManager.kt index 8be2cfb2f0..fff50bcec9 100644 --- a/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/iflytek/WakeManager.kt +++ b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/iflytek/WakeManager.kt @@ -1,63 +1,23 @@ package com.mogo.mgintelligent.speech.iflytek -import android.Manifest -import android.annotation.SuppressLint 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 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.iflytek.aikit.core.BaseLibrary import com.iflytek.aikit.core.CoreListener import com.iflytek.aikit.core.ErrType import com.iflytek.aikit.core.LogLvl 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.FileUtils import com.mogo.eagle.core.utilcode.util.ResourceUtils import com.mogo.eagle.core.utilcode.util.ThreadUtils import com.mogo.eagle.core.utilcode.util.Utils -import java.io.BufferedWriter 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 { - private 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 const val TAG = "WakeManager" private val coreListener = CoreListener { type, code -> Log.i(TAG, "core listener code:$code") @@ -66,10 +26,10 @@ object WakeManager { ErrType.AUTH -> { if (code == 0) { Log.i(TAG, "SDK授权成功") + WakeUpManager.startThread() } else { Log.i(TAG, "SDK授权失败,授权码为:$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, 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, 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 - * 音频要求16bit,16K,单声道的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) { @@ -342,6 +50,7 @@ object WakeManager { file.mkdirs() } AiHelper.getInst().setLogInfo(LogLvl.VERBOSE, 1, "${workDir}aeeLog.txt") + AiHelper.getInst().setLogMode(2) //设定初始化参数 val params = BaseLibrary.Params.builder() @@ -377,7 +86,7 @@ object WakeManager { val assets = Utils.getApp().assets.list(dir)?.toMutableList() val tartgetFile = File(targetDir) if(tartgetFile.isDirectory){ - tartgetFile.listFiles { dir, name -> + tartgetFile.listFiles { _, name -> (assets?.contains(name)?:true).apply { assets?.remove(name) } @@ -393,8 +102,4 @@ object WakeManager { } throw IllegalArgumentException("找不到 Application") } - - fun getIvwDir(): String { - return getWorkDir()+File.separator+"ivw" - } } \ No newline at end of file diff --git a/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/iflytek/WakeUpManager.kt b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/iflytek/WakeUpManager.kt new file mode 100644 index 0000000000..f27833332d --- /dev/null +++ b/libraries/mogo-speech/src/main/java/com/mogo/mgintelligent/speech/iflytek/WakeUpManager.kt @@ -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, 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, 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 + * 音频要求16bit,16K,单声道的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" + } + +} \ No newline at end of file