diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 9f7690714..6879ee469 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -65,6 +65,9 @@ dependencies {
// 添加legacy-support-v4依赖
implementation("androidx.legacy:legacy-support-v4:1.0.0")
+ // 添加 AndroidX Security 加密 SharedPreferences 依赖
+ implementation("androidx.security:security-crypto:1.1.0-alpha06")
+
// // 添加火山语音合成SDK依赖
// implementation("com.bytedance.speechengine:speechengine_tts_tob:5.4.8")
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 97a32b9e3..88f1fef92 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -17,8 +17,10 @@
-
-
+
+
diff --git a/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt b/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt
index 1eff8c8be..9ed28070d 100644
--- a/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt
+++ b/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt
@@ -1,20 +1,15 @@
package com.example.deep_voice
-import android.util.Log
+import android.content.Context
+import com.example.deep_voice.core.utils.FileLogger
import com.microsoft.cognitiveservices.speech.*
import com.microsoft.cognitiveservices.speech.audio.*
import com.microsoft.cognitiveservices.speech.util.EventHandler
import java.util.concurrent.ExecutionException
-import android.media.audiofx.AcousticEchoCanceler
-import android.media.AudioRecord
-import android.media.MediaRecorder
-import android.media.AudioFormat
-import java.util.concurrent.Executors
-class AzureAsrHelper {
+class AzureAsrHelper(private val context: Context) {
private var recognizer: SpeechRecognizer? = null
private var speechConfig: SpeechConfig? = null
- private var audioConfig: AudioConfig? = null
private val TAG = "AzureAsrHelper"
private var isContinuousRecognitionActive = false
private var currentLanguage = "zh-CN"
@@ -22,34 +17,26 @@ class AzureAsrHelper {
private var serviceRegion = ""
private var isAutoDetectLanguage = false
private var supportedLanguages = arrayOf("zh-CN", "en-US")
-
- // 回音消除相关
- private var echoCanceler: AcousticEchoCanceler? = null
- private var audioSessionId: Int = -1
- private var isEchoCancellationEnabled = false
-
- // 直接管理 AudioRecord 和 PushStream
- private var audioRecord: AudioRecord? = null
- private var pushStream: PushAudioInputStream? = null
- private var isRecording = false
- private val executorService = Executors.newSingleThreadExecutor()
- // 初始化 SDK
+ // 初始化SDK并创建recognizer
fun initialize(subscriptionKey: String, serviceRegion: String,
supportedLanguages: Array = arrayOf("zh-CN", "en-US")): Boolean {
try {
- Log.d(TAG, "开始初始化 Azure 语音服务: region=$serviceRegion, supportedLanguages=${supportedLanguages.joinToString()}")
+ FileLogger.d(TAG, "初始化 Azure 语音服务")
// 检查配置是否为空
if (subscriptionKey.isEmpty() || serviceRegion.isEmpty()) {
- Log.e(TAG, "Azure 配置信息不完整: subscriptionKey=${subscriptionKey.isEmpty()}, serviceRegion=${serviceRegion.isEmpty()}")
+ FileLogger.e(TAG, "Azure 配置信息不完整")
return false
}
+ // 释放之前的资源
+ dispose()
+
this.subscriptionKey = subscriptionKey
this.serviceRegion = serviceRegion
- // 设置支持的语言列表
+ // 设置语言
if (supportedLanguages.isNotEmpty()) {
this.supportedLanguages = supportedLanguages
}
@@ -62,320 +49,178 @@ class AzureAsrHelper {
this.currentLanguage = supportedLanguages[0]
}
- // 释放之前的资源
- dispose()
-
// 创建语音配置
speechConfig = SpeechConfig.fromSubscription(subscriptionKey, serviceRegion)
- // 设置为 CONVERSATION 模式
- speechConfig?.setProperty(PropertyId.SpeechServiceConnection_RecoMode, "CONVERSATION")
-
+ // 设置语言配置
if (isAutoDetectLanguage) {
- // 设置自动语言检测模式为连续检测
+ // 设置自动语言检测
speechConfig?.setProperty(PropertyId.SpeechServiceConnection_LanguageIdMode, "Continuous")
} else {
// 设置指定的识别语言
speechConfig?.speechRecognitionLanguage = currentLanguage
}
- // 创建 PushAudioInputStream 和 AudioConfig
+ // 创建识别器
try {
- setupAudioInput()
- Log.d(TAG, "成功创建音频配置")
+ if (isAutoDetectLanguage) {
+ val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
+ recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
+ } else {
+ recognizer = SpeechRecognizer(speechConfig)
+ }
+
+ FileLogger.d(TAG, "Azure 语音服务初始化成功")
+ return true
} catch (e: Exception) {
- Log.e(TAG, "创建音频配置失败: ${e.message}", e)
+ FileLogger.e(TAG, "创建识别器失败: ${e.message}")
return false
}
-
- // 创建识别器
- Log.d(TAG, "创建语音识别器")
- if (isAutoDetectLanguage) {
- // 创建自动语言检测配置
- val autoDetectSourceLanguageConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
- recognizer = SpeechRecognizer(speechConfig, autoDetectSourceLanguageConfig, audioConfig)
- } else {
- recognizer = SpeechRecognizer(speechConfig, audioConfig)
- }
-
- Log.d(TAG, "Azure 语音服务初始化成功")
- return true
} catch (e: Exception) {
- Log.e(TAG, "初始化失败: ${e.message}", e)
+ FileLogger.e(TAG, "初始化失败: ${e.message}")
return false
}
}
- // 设置音频输入
- private fun setupAudioInput() {
- // 释放之前的资源
- releaseAudioResources()
-
- // 创建 PushAudioInputStream
- pushStream = AudioInputStream.createPushStream()
-
- // 创建 AudioConfig
- audioConfig = AudioConfig.fromStreamInput(pushStream)
-
- // 创建 AudioRecord
- val sampleRate = 16000
- val channelConfig = AudioFormat.CHANNEL_IN_MONO
- val audioFormat = AudioFormat.ENCODING_PCM_16BIT
- val bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
-
- if (bufferSize <= 0) {
- Log.e(TAG, "无法获取最小缓冲区大小")
- throw RuntimeException("无法获取最小缓冲区大小")
- }
-
- audioRecord = AudioRecord(
- MediaRecorder.AudioSource.VOICE_COMMUNICATION,
- sampleRate,
- channelConfig,
- audioFormat,
- bufferSize
- )
-
- if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) {
- Log.e(TAG, "AudioRecord 初始化失败")
- audioRecord?.release()
- audioRecord = null
- throw RuntimeException("AudioRecord 初始化失败")
+ // 重置 recognizer
+ private fun resetRecognizer(): Boolean {
+ try {
+ // 释放之前的 recognizer
+ recognizer?.close()
+ recognizer = null
+
+ // 使用现有配置重新创建 recognizer
+ if (speechConfig != null) {
+ if (isAutoDetectLanguage) {
+ val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
+ recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
+ } else {
+ recognizer = SpeechRecognizer(speechConfig)
+ }
+ return true
+ } else {
+ FileLogger.e(TAG, "语音配置未初始化")
+ return false
+ }
+ } catch (e: Exception) {
+ FileLogger.e(TAG, "重置识别器失败: ${e.message}")
+ return false
}
-
- // 获取音频会话 ID 并设置回音消除
- audioSessionId = audioRecord?.audioSessionId ?: -1
- setupEchoCancellation()
}
// 开始一次性语音识别
- fun recognizeOnce(language: String? = null, callback: RecognizeCallback) {
- if (recognizer == null) {
- callback.onError("SpeechRecognizer 未初始化")
+ fun recognizeOnce(callback: RecognizeCallback) {
+ if (speechConfig == null) {
+ callback.onError("语音服务未初始化")
return
}
- // 如果指定了新的语言,且不是自动检测模式,需要重新初始化
- if (language != null && language != currentLanguage && !isAutoDetectLanguage) {
- // 使用指定的语言创建新的语言列表
- val newLanguages = arrayOf(language)
- initialize(subscriptionKey, serviceRegion, newLanguages)
+ // 重置 recognizer
+ if (!resetRecognizer()) {
+ callback.onError("重置识别器失败")
+ return
}
try {
- // 开始录音并推送数据
- startRecording()
-
+ // 执行识别
val result = recognizer?.recognizeOnceAsync()?.get()
- // 停止录音
- stopRecording()
-
if (result != null && result.reason == ResultReason.RecognizedSpeech) {
- val autoDetectSourceLanguageResult = AutoDetectSourceLanguageResult.fromResult(result)
- val detectedLanguage = autoDetectSourceLanguageResult?.language ?: ""
- Log.d(TAG, "识别结果: ${result.text}, 检测到的语言: $detectedLanguage")
- callback.onResult(result.text, detectedLanguage)
+ val detectedLanguage = AutoDetectSourceLanguageResult.fromResult(result)?.language
+
+ callback.onResult(result.text, detectedLanguage ?: "")
} else {
callback.onError("未能识别语音")
}
} catch (e: Exception) {
- stopRecording()
callback.onError("识别异常: ${e.message}")
}
}
// 开始连续语音识别
fun startContinuousRecognition(callback: ContinuousRecognizeCallback): Boolean {
- if (recognizer == null) {
- Log.e(TAG, "SpeechRecognizer 未初始化")
- callback.onError("SpeechRecognizer 未初始化")
+ if (speechConfig == null) {
+ callback.onError("语音服务未初始化")
return false
}
// 如果已经在进行连续识别,先停止
if (isContinuousRecognitionActive) {
- Log.d(TAG, "已经在进行连续识别,先停止")
stopContinuousRecognition(callback)
}
-
+
+ // 重置 recognizer
+ if (!resetRecognizer()) {
+ callback.onError("重置识别器失败")
+ return false
+ }
+
try {
- Log.d(TAG, "开始重置识别器和音频资源")
-
- // 完全释放所有资源,确保干净的状态
- try {
- // 释放识别器
- recognizer?.close()
- recognizer = null
-
- // 释放音频资源
- releaseAudioResources()
-
- // 确保状态标志正确
- isContinuousRecognitionActive = false
- isRecording = false
-
- Log.d(TAG, "资源释放完成,准备重新创建")
- } catch (e: Exception) {
- Log.e(TAG, "释放资源失败: ${e.message}", e)
- // 继续执行,尝试重新创建
- }
-
- // 重新创建音频配置
- try {
- setupAudioInput()
- Log.d(TAG, "音频配置创建成功")
- } catch (e: Exception) {
- Log.e(TAG, "创建音频配置失败: ${e.message}", e)
- callback.onError("创建音频配置失败: ${e.message}")
- return false
- }
-
- // 重新创建识别器
- try {
- Log.d(TAG, "创建新的语音识别器")
- if (isAutoDetectLanguage) {
- // 创建自动语言检测配置
- val autoDetectSourceLanguageConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
- recognizer = SpeechRecognizer(speechConfig, autoDetectSourceLanguageConfig, audioConfig)
- } else {
- recognizer = SpeechRecognizer(speechConfig, audioConfig)
- }
- } catch (e: Exception) {
- Log.e(TAG, "创建识别器失败: ${e.message}", e)
- callback.onError("创建识别器失败: ${e.message}")
- return false
- }
-
// 设置识别事件处理
- recognizer?.let { recognizer ->
- // 最终识别结果
- recognizer.recognized.addEventListener(
- EventHandler { _, event ->
- if (event.result.reason == ResultReason.RecognizedSpeech) {
- val autoDetectSourceLanguageResult = AutoDetectSourceLanguageResult.fromResult(event.result)
- val detectedLanguage = autoDetectSourceLanguageResult?.language ?: ""
- Log.d(TAG, "识别结果: ${event.result.text}, 检测到的语言: $detectedLanguage")
- callback.onResult(event.result.text, detectedLanguage)
+ // 最终识别结果
+ recognizer?.recognized?.addEventListener(
+ EventHandler { _, event ->
+ if (event.result.reason == ResultReason.RecognizedSpeech) {
+ val detectedLanguage = if (isAutoDetectLanguage) {
+ AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: ""
} else {
- Log.d(TAG, "识别结果原因: ${event.result.reason}")
+ currentLanguage
}
+ callback.onResult(event.result.text, detectedLanguage)
}
- )
+ }
+ )
- // 识别中事件
- recognizer.recognizing.addEventListener(
- EventHandler { _, event ->
- if (event.result.reason == ResultReason.RecognizingSpeech) {
- val autoDetectSourceLanguageResult = AutoDetectSourceLanguageResult.fromResult(event.result)
- val detectedLanguage = autoDetectSourceLanguageResult?.language ?: ""
- // if (isAutoDetectLanguage) {
- // Log.d(TAG, "正在识别: ${event.result.text}, 检测到的语言: $detectedLanguage")
- // }
- callback.onRecognizing(event.result.text, detectedLanguage)
+ // 识别中事件
+ recognizer?.recognizing?.addEventListener(
+ EventHandler { _, event ->
+ if (event.result.reason == ResultReason.RecognizingSpeech) {
+ val detectedLanguage = if (isAutoDetectLanguage) {
+ AutoDetectSourceLanguageResult.fromResult(event.result)?.language ?: ""
+ } else {
+ currentLanguage
}
+ callback.onRecognizing(event.result.text, detectedLanguage)
}
- )
-
- // 会话开始事件
- recognizer.sessionStarted.addEventListener(
- EventHandler { _, _ ->
- Log.d(TAG, "会话开始")
- isContinuousRecognitionActive = true
- callback.onSessionStarted()
- }
- )
+ }
+ )
- // 会话结束事件
- recognizer.sessionStopped.addEventListener(
- EventHandler { _, _ ->
- Log.d(TAG, "会话结束")
- isContinuousRecognitionActive = false
- stopRecording()
- callback.onSessionStopped()
- }
- )
+ // 会话事件
+ recognizer?.sessionStarted?.addEventListener(
+ EventHandler { _, _ ->
+ isContinuousRecognitionActive = true
+ callback.onSessionStarted()
+ }
+ )
- // 取消事件
- recognizer.canceled.addEventListener(
- EventHandler { _, event ->
- val errorDetails = if (event.reason == CancellationReason.Error) event.errorDetails else ""
- Log.e(TAG, "识别取消: ${event.reason}, 详情: $errorDetails")
- callback.onCanceled(event.reason.toString(), errorDetails)
- isContinuousRecognitionActive = false
- stopRecording()
- }
- )
+ recognizer?.sessionStopped?.addEventListener(
+ EventHandler { _, _ ->
+ isContinuousRecognitionActive = false
+ callback.onSessionStopped()
+ }
+ )
- // 开始录音
- startRecording()
-
- // 开始连续识别
- Log.d(TAG, "开始连续识别")
- recognizer.startContinuousRecognitionAsync().get()
- Log.d(TAG, "连续识别已启动")
- isContinuousRecognitionActive = true
- }
+ // 取消事件
+ recognizer?.canceled?.addEventListener(
+ EventHandler { _, event ->
+ val errorDetails = if (event.reason == CancellationReason.Error) event.errorDetails else ""
+ callback.onCanceled(event.reason.toString(), errorDetails)
+ isContinuousRecognitionActive = false
+ }
+ )
+
+ // 开始连续识别
+ recognizer?.startContinuousRecognitionAsync()?.get()
+ isContinuousRecognitionActive = true
return true
} catch (e: Exception) {
- Log.e(TAG, "开始连续识别失败: ${e.message}", e)
callback.onError("开始连续识别失败: ${e.message}")
isContinuousRecognitionActive = false
- stopRecording()
return false
}
}
- // 开始录音并推送数据到 PushStream
- private fun startRecording() {
- if (isRecording || audioRecord == null || pushStream == null) {
- return
- }
-
- isRecording = true
- audioRecord?.startRecording()
-
- executorService.submit {
- val bufferSize = AudioRecord.getMinBufferSize(
- 16000,
- AudioFormat.CHANNEL_IN_MONO,
- AudioFormat.ENCODING_PCM_16BIT
- )
- val buffer = ByteArray(bufferSize)
-
- while (isRecording && audioRecord != null && pushStream != null) {
- try {
- val readBytes = audioRecord?.read(buffer, 0, buffer.size) ?: 0
- if (readBytes > 0) {
- // 修复:PushAudioInputStream.write() 只接受一个 ByteArray 参数
- // 如果读取的数据长度等于缓冲区大小,直接写入
- if (readBytes == buffer.size) {
- pushStream?.write(buffer)
- } else {
- // 否则,创建一个新的数组,只包含实际读取的数据
- val audioData = buffer.copyOf(readBytes)
- pushStream?.write(audioData)
- }
- }
- } catch (e: Exception) {
- Log.e(TAG, "读取音频数据失败: ${e.message}", e)
- break
- }
- }
- }
- }
-
- // 停止录音
- private fun stopRecording() {
- isRecording = false
- try {
- audioRecord?.stop()
- } catch (e: Exception) {
- Log.e(TAG, "停止录音失败: ${e.message}", e)
- }
- }
-
// 停止连续语音识别
fun stopContinuousRecognition(callback: ContinuousRecognizeCallback): Boolean {
if (!isContinuousRecognitionActive || recognizer == null) {
@@ -385,13 +230,11 @@ class AzureAsrHelper {
try {
recognizer?.stopContinuousRecognitionAsync()?.get()
isContinuousRecognitionActive = false
- stopRecording()
callback.onSessionStopped()
return true
} catch (e: Exception) {
callback.onError("停止连续识别失败: ${e.message}")
isContinuousRecognitionActive = false
- stopRecording()
return false
}
}
@@ -400,98 +243,23 @@ class AzureAsrHelper {
fun isContinuousRecognitionActive(): Boolean {
return isContinuousRecognitionActive
}
-
- // 获取当前语言
- fun getCurrentLanguage(): String {
- return currentLanguage
- }
- // 释放资源
+ // 释放所有资源
fun dispose() {
try {
- // 添加标志,避免重复释放
- if (recognizer == null && audioRecord == null && pushStream == null) {
- Log.d(TAG, "资源已经释放,跳过重复释放")
- return
- }
-
- Log.d(TAG, "开始释放资源")
-
- if (isContinuousRecognitionActive && recognizer != null) {
- try {
- recognizer?.stopContinuousRecognitionAsync()?.get()
- Log.d(TAG, "连续识别已停止")
- } catch (e: Exception) {
- Log.e(TAG, "停止连续识别失败: ${e.message}", e)
- }
- }
-
- try {
- recognizer?.close()
- recognizer = null
- Log.d(TAG, "识别器已关闭")
- } catch (e: Exception) {
- Log.e(TAG, "关闭识别器失败: ${e.message}", e)
- }
+ recognizer?.close()
+ recognizer = null
- releaseAudioResources()
-
- try {
- speechConfig?.close()
- speechConfig = null
- Log.d(TAG, "语音配置已关闭")
- } catch (e: Exception) {
- Log.e(TAG, "关闭语音配置失败: ${e.message}", e)
- }
+ speechConfig?.close()
+ speechConfig = null
isContinuousRecognitionActive = false
- Log.d(TAG, "所有资源已释放")
+
+ FileLogger.d(TAG, "语音识别资源已释放")
} catch (e: Exception) {
- Log.e(TAG, "释放资源时出错: ${e.message}", e)
+ FileLogger.e(TAG, "释放资源出错: ${e.message}")
}
}
-
- // 释放音频相关资源
- private fun releaseAudioResources() {
- // 停止录音
- stopRecording()
-
- // 释放 AudioRecord
- if (audioRecord != null) {
- try {
- Log.d(TAG, "释放 AudioRecord")
- audioRecord?.release()
- audioRecord = null
- } catch (e: Exception) {
- Log.e(TAG, "释放 AudioRecord 失败: ${e.message}", e)
- }
- }
-
- // 释放 AudioConfig
- if (audioConfig != null) {
- try {
- Log.d(TAG, "释放 AudioConfig")
- audioConfig?.close()
- audioConfig = null
- } catch (e: Exception) {
- Log.e(TAG, "释放 AudioConfig 失败: ${e.message}", e)
- }
- }
-
- // 释放 PushStream
- if (pushStream != null) {
- try {
- Log.d(TAG, "释放 PushStream")
- pushStream?.close()
- pushStream = null
- } catch (e: Exception) {
- Log.e(TAG, "释放 PushStream 失败: ${e.message}", e)
- }
- }
-
- // 释放回音消除资源
- releaseEchoCancellation()
- }
// 一次性识别回调接口
interface RecognizeCallback {
@@ -509,127 +277,4 @@ class AzureAsrHelper {
fun onError(error: String)
}
- // 设置回音消除
- private fun setupEchoCancellation() {
- try {
- // 检查设备是否支持回音消除
- if (AcousticEchoCanceler.isAvailable()) {
- Log.d(TAG, "设备支持回音消除")
-
- // 使用实际的 AudioRecord 会话 ID
- if (audioSessionId != -1) {
- echoCanceler = AcousticEchoCanceler.create(audioSessionId)
- if (echoCanceler != null) {
- echoCanceler?.enabled = true
- isEchoCancellationEnabled = echoCanceler?.enabled == true
- Log.d(TAG, "回音消除已启用: $isEchoCancellationEnabled")
- } else {
- Log.e(TAG, "创建回音消除器失败")
- }
- } else {
- Log.e(TAG, "无效的音频会话 ID")
- }
- } else {
- Log.d(TAG, "设备不支持回音消除")
- }
- } catch (e: Exception) {
- Log.e(TAG, "设置回音消除时出错: ${e.message}", e)
- }
- }
-
- // 释放回音消除资源
- private fun releaseEchoCancellation() {
- try {
- if (echoCanceler != null) {
- echoCanceler?.enabled = false
- echoCanceler?.release()
- echoCanceler = null
- isEchoCancellationEnabled = false
- audioSessionId = -1
- Log.d(TAG, "回音消除资源已释放")
- }
- } catch (e: Exception) {
- Log.e(TAG, "释放回音消除资源时出错: ${e.message}", e)
- }
- }
-
- // 设置回音消除开关
- fun setEchoCancellationEnabled(enabled: Boolean): Boolean {
- try {
- if (echoCanceler != null) {
- echoCanceler?.enabled = enabled
- isEchoCancellationEnabled = echoCanceler?.enabled == true
- Log.d(TAG, "回音消除状态已设置为: $isEchoCancellationEnabled")
- return isEchoCancellationEnabled == enabled
- } else if (enabled) {
- // 如果当前没有回音消除器但需要启用,尝试创建
- if (audioSessionId != -1) {
- setupEchoCancellation()
- return isEchoCancellationEnabled
- }
- }
- return false
- } catch (e: Exception) {
- Log.e(TAG, "设置回音消除状态时出错: ${e.message}", e)
- return false
- }
- }
-
- // 获取回音消除状态
- fun isEchoCancellationEnabled(): Boolean {
- return isEchoCancellationEnabled
- }
-
- // 设置支持的语言列表(用于自动语言检测)
- fun setSupportedLanguages(languages: Array) {
- if (languages.isNotEmpty()) {
- Log.d(TAG, "设置支持的语言列表: ${languages.joinToString()}")
-
- // 如果当前是自动检测模式且识别器已初始化,需要重新初始化以应用新的语言列表
- if (isAutoDetectLanguage && recognizer != null) {
- Log.d(TAG, "自动检测模式下更新语言列表,重新初始化识别器")
- initialize(subscriptionKey, serviceRegion, languages)
- } else {
- // 仅更新语言列表,不重新初始化
- supportedLanguages = languages
-
- // 如果不是自动检测模式且只有一种语言,更新当前语言
- if (!isAutoDetectLanguage && languages.size == 1) {
- currentLanguage = languages[0]
- }
- }
- }
- }
-
- // 获取当前是否启用自动语言检测
- fun isAutoDetectLanguageEnabled(): Boolean {
- return isAutoDetectLanguage
- }
-
- // 启用自动语言检测并设置支持的语言列表
- fun enableAutoDetectLanguage(enable: Boolean, languages: Array? = null): Boolean {
- // 准备语言列表
- val languagesToUse = if (languages != null && languages.isNotEmpty()) {
- // 如果提供了新的语言列表,使用新的列表
- languages
- } else if (enable && supportedLanguages.size < 2) {
- // 如果要启用自动检测但当前支持的语言少于2种,使用默认的两种语言
- arrayOf("zh-CN", "en-US")
- } else {
- // 否则使用当前的语言列表
- supportedLanguages
- }
-
- // 如果要禁用自动检测,但提供了多种语言,只使用第一种语言
- val finalLanguages = if (!enable && languagesToUse.size > 1) {
- arrayOf(languagesToUse[0])
- } else {
- languagesToUse
- }
-
- Log.d(TAG, "设置自动语言检测: enable=$enable, languages=${finalLanguages.joinToString()}")
-
- // 重新初始化以应用新的设置
- return initialize(subscriptionKey, serviceRegion, finalLanguages)
- }
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt b/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt
index b05e1a442..287529fdc 100644
--- a/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt
+++ b/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt
@@ -5,7 +5,7 @@ import android.media.AudioAttributes
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.os.Build
-import android.util.Log
+import com.example.deep_voice.core.utils.FileLogger
import com.microsoft.cognitiveservices.speech.*
import com.microsoft.cognitiveservices.speech.audio.*
import java.util.concurrent.Future
@@ -17,12 +17,11 @@ import java.util.concurrent.Semaphore
* 该类封装了微软语音 SDK 的 TTS 功能,提供简单的接口供 Flutter 调用
* 基于微软官方文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-speech-synthesis
*/
-class AzureTtsHelper {
+class AzureTtsHelper(private val context: Context) {
private val TAG = "AzureTtsHelper"
private var speechConfig: SpeechConfig? = null
private var synthesizer: SpeechSynthesizer? = null
private var isInitialized = false
- private var context: Context? = null
private var audioManager: AudioManager? = null
// 音频输出设备类型
@@ -50,14 +49,11 @@ class AzureTtsHelper {
* @param subscriptionKey Azure 语音服务订阅密钥
* @param serviceRegion Azure 语音服务区域
* @param language 可选,默认语言,默认为 "zh-CN"
- * @param context Android 上下文,用于获取音频服务
*/
- fun initialize(subscriptionKey: String, serviceRegion: String, language: String = "zh-CN", context: Context? = null): Boolean {
+ fun initialize(subscriptionKey: String, serviceRegion: String, language: String = "zh-CN"): Boolean {
try {
- this.context = context
- if (context != null) {
- audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
- }
+ audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+
// 创建语音配置
speechConfig = SpeechConfig.fromSubscription(subscriptionKey, serviceRegion)
@@ -82,7 +78,7 @@ class AzureTtsHelper {
synthesizer?.SynthesisCanceled?.addEventListener { _, _ -> semaphore.release() }
isInitialized = true
- Log.d(TAG, "TTS 引擎初始化成功")
+ FileLogger.d(TAG, "TTS 引擎初始化成功")
// 设置默认音频输出类型为自动
if (audioManager != null) {
@@ -91,7 +87,7 @@ class AzureTtsHelper {
return true
} catch (e: Exception) {
- Log.e(TAG, "TTS 引擎初始化失败: ${e.message}")
+ FileLogger.e(TAG, "TTS 引擎初始化失败: ${e.message}, ${serviceRegion}")
e.printStackTrace()
return false
}
@@ -105,12 +101,12 @@ class AzureTtsHelper {
*/
fun setAudioOutputType(outputType: AudioOutputType): Boolean {
if (!isInitialized) {
- Log.e(TAG, "TTS 引擎尚未初始化")
+ FileLogger.e(TAG, "TTS 引擎尚未初始化")
return false
}
if (audioManager == null) {
- Log.e(TAG, "AudioManager 未初始化,无法设置音频输出设备")
+ FileLogger.e(TAG, "AudioManager 未初始化,无法设置音频输出设备")
return false
}
@@ -122,13 +118,13 @@ class AzureTtsHelper {
// 使用扬声器
audioManager?.mode = AudioManager.MODE_NORMAL
audioManager?.isSpeakerphoneOn = true
- Log.d(TAG, "已设置音频输出设备为扬声器")
+ FileLogger.d(TAG, "已设置音频输出设备为扬声器")
}
AudioOutputType.EARPIECE -> {
// 使用听筒
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
audioManager?.isSpeakerphoneOn = false
- Log.d(TAG, "已设置音频输出设备为听筒")
+ FileLogger.d(TAG, "已设置音频输出设备为听筒")
}
AudioOutputType.AUTO -> {
// 检查是否有耳机连接
@@ -136,12 +132,12 @@ class AzureTtsHelper {
// 有耳机,使用耳机
audioManager?.mode = AudioManager.MODE_NORMAL
audioManager?.isSpeakerphoneOn = false
- Log.d(TAG, "已设置音频输出设备为耳机")
+ FileLogger.d(TAG, "已设置音频输出设备为耳机")
} else {
// 无耳机,使用听筒
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
audioManager?.isSpeakerphoneOn = false
- Log.d(TAG, "已设置音频输出设备为听筒")
+ FileLogger.d(TAG, "已设置音频输出设备为听筒")
}
}
}
@@ -151,7 +147,7 @@ class AzureTtsHelper {
return true
} catch (e: Exception) {
- Log.e(TAG, "设置音频输出设备失败: ${e.message}")
+ FileLogger.e(TAG, "设置音频输出设备失败: ${e.message}")
e.printStackTrace()
return false
}
@@ -196,9 +192,9 @@ class AzureTtsHelper {
synthesizer?.SynthesisCompleted?.addEventListener { _, _ -> semaphore.release() }
synthesizer?.SynthesisCanceled?.addEventListener { _, _ -> semaphore.release() }
- Log.d(TAG, "已重新创建语音合成器")
+ FileLogger.d(TAG, "已重新创建语音合成器")
} catch (e: Exception) {
- Log.e(TAG, "重新创建语音合成器失败: ${e.message}")
+ FileLogger.e(TAG, "重新创建语音合成器失败: ${e.message}")
e.printStackTrace()
}
}
@@ -210,10 +206,13 @@ class AzureTtsHelper {
*/
fun setVoice(voiceName: String): Boolean {
if (!isInitialized) {
- Log.e(TAG, "TTS 引擎尚未初始化")
+ FileLogger.e(TAG, "TTS 引擎尚未初始化")
return false
}
-
+ if (voiceName == currentVoiceName) {
+ FileLogger.d(TAG, "已设置语音: $voiceName")
+ return true
+ }
try {
currentVoiceName = voiceName
speechConfig?.setSpeechSynthesisVoiceName(voiceName)
@@ -221,10 +220,10 @@ class AzureTtsHelper {
// 重新创建合成器以应用新的语音设置
recreateSynthesizer()
- Log.d(TAG, "已设置语音: $voiceName")
+ FileLogger.d(TAG, "已设置语音: $voiceName")
return true
} catch (e: Exception) {
- Log.e(TAG, "设置语音失败: ${e.message}")
+ FileLogger.e(TAG, "设置语音失败: ${e.message}")
e.printStackTrace()
return false
}
@@ -239,7 +238,7 @@ class AzureTtsHelper {
*/
fun setSpeechParams(rate: Int = 0, pitch: Int = 0, volume: Int = 100): Boolean {
if (!isInitialized) {
- Log.e(TAG, "TTS 引擎尚未初始化")
+ FileLogger.e(TAG, "TTS 引擎尚未初始化")
return false
}
@@ -248,10 +247,10 @@ class AzureTtsHelper {
currentPitch = if (pitch == 0) "0%" else "${(pitch * 0.5).toInt()}%"
currentVolume = "${volume.coerceIn(0, 100)}%"
- Log.d(TAG, "已设置语音参数: 语速=$currentSpeechRate, 音调=$currentPitch, 音量=$currentVolume")
+ FileLogger.d(TAG, "已设置语音参数: 语速=$currentSpeechRate, 音调=$currentPitch, 音量=$currentVolume")
return true
} catch (e: Exception) {
- Log.e(TAG, "设置语音参数失败: ${e.message}")
+ FileLogger.e(TAG, "设置语音参数失败: ${e.message}")
e.printStackTrace()
return false
}
@@ -270,12 +269,8 @@ class AzureTtsHelper {
}
try {
- Log.d(TAG, "开始合成文本: $text")
+ FileLogger.d(TAG, "开始合成文本: $text")
- // 如果设置为自动模式,检查耳机状态并更新设置
- if (currentAudioOutputType == AudioOutputType.AUTO) {
- setAudioOutputType(AudioOutputType.AUTO)
- }
// 生成 SSML
val ssml = generateSsml(text)
@@ -283,7 +278,7 @@ class AzureTtsHelper {
// 使用 SSML 合成语音
speakSsml(ssml, callback)
} catch (e: Exception) {
- Log.e(TAG, "语音合成异常: ${e.message}")
+ FileLogger.e(TAG, "语音合成异常: ${e.message}")
e.printStackTrace()
callback.onError("语音合成异常: ${e.message}")
}
@@ -320,7 +315,7 @@ class AzureTtsHelper {
}
try {
- Log.d(TAG, "开始合成 SSML")
+ FileLogger.d(TAG, "开始合成 SSML")
// 重置信号量
semaphore.drainPermits()
@@ -339,29 +334,29 @@ class AzureTtsHelper {
when (result.reason) {
ResultReason.SynthesizingAudioCompleted -> {
- Log.d(TAG, "语音合成完成,音频长度: ${result.audioLength / 10000}ms,音频数据大小: ${result.audioData?.size ?: 0} 字节")
+ FileLogger.d(TAG, "语音合成完成,音频长度: ${result.audioLength / 10000}ms,音频数据大小: ${result.audioData?.size ?: 0} 字节")
callback.onSuccess("语音合成完成")
}
ResultReason.Canceled -> {
val cancellation = SpeechSynthesisCancellationDetails.fromResult(result)
- Log.e(TAG, "语音合成取消: ${cancellation.reason}, ${cancellation.errorDetails}")
+ FileLogger.e(TAG, "语音合成取消: ${cancellation.reason}, ${cancellation.errorDetails}")
callback.onError("语音合成取消: ${cancellation.reason}, ${cancellation.errorDetails}")
}
else -> {
- Log.e(TAG, "语音合成失败: ${result.reason}")
+ FileLogger.e(TAG, "语音合成失败: ${result.reason}")
callback.onError("语音合成失败: ${result.reason}")
}
}
result.close()
} catch (e: Exception) {
- Log.e(TAG, "处理语音合成结果异常: ${e.message}")
+ FileLogger.e(TAG, "处理语音合成结果异常: ${e.message}")
e.printStackTrace()
callback.onError("处理语音合成结果异常: ${e.message}")
}
}.start()
} catch (e: Exception) {
- Log.e(TAG, "语音合成异常: ${e.message}")
+ FileLogger.e(TAG, "语音合成异常: ${e.message}")
e.printStackTrace()
callback.onError("语音合成异常: ${e.message}")
}
@@ -372,17 +367,17 @@ class AzureTtsHelper {
*/
fun stopSpeaking(): Boolean {
if (!isInitialized) {
- Log.e(TAG, "TTS 引擎尚未初始化")
+ FileLogger.e(TAG, "TTS 引擎尚未初始化")
return false
}
try {
synthesizer?.StopSpeakingAsync()
- Log.d(TAG, "已停止语音合成")
+ FileLogger.d(TAG, "已停止语音合成")
return true
} catch (e: Exception) {
- Log.e(TAG, "停止语音合成失败: ${e.message}")
+ FileLogger.e(TAG, "停止语音合成失败: ${e.message}")
e.printStackTrace()
return false
}
@@ -404,9 +399,9 @@ class AzureTtsHelper {
}
isInitialized = false
- Log.d(TAG, "TTS 引擎已释放")
+ FileLogger.d(TAG, "TTS 引擎已释放")
} catch (e: Exception) {
- Log.e(TAG, "释放 TTS 引擎失败: ${e.message}")
+ FileLogger.e(TAG, "释放 TTS 引擎失败: ${e.message}")
e.printStackTrace()
}
}
diff --git a/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt b/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt
index 968652ae1..63f10c57b 100644
--- a/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt
@@ -17,6 +17,9 @@ import android.os.Handler
import android.os.Looper
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
+import androidx.security.crypto.EncryptedSharedPreferences
+import androidx.security.crypto.MasterKey
+import com.example.deep_voice.core.utils.FileLogger
class MainActivity: FlutterActivity() {
@@ -140,15 +143,112 @@ class MainActivity: FlutterActivity() {
var azureSpeechKey: String = ""
var azureSpeechRegion: String = ""
var volcanoAiApiKey: String = ""
+
+ // 安全存储相关常量
+ private const val SECURE_PREFS_FILENAME = "deep_voice_secure_prefs"
+ private const val KEY_AZURE_SPEECH_KEY = "azure_speech_key"
+ private const val KEY_AZURE_SPEECH_REGION = "azure_speech_region"
+ private const val KEY_VOLCANO_AI_API_KEY = "volcano_ai_api_key"
+
+ // 会话管理
+ private const val KEY_SESSION_ID = "session_id"
+ private var currentSessionId = ""
+
+ /**
+ * 将密钥安全地存储到 EncryptedSharedPreferences
+ */
+ fun saveKeysToSecureStorage(context: Context) {
+ try {
+ // 创建或获取主密钥
+ val masterKey = MasterKey.Builder(context)
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
+ .build()
+
+ // 创建 EncryptedSharedPreferences
+ val sharedPreferences = EncryptedSharedPreferences.create(
+ context,
+ SECURE_PREFS_FILENAME,
+ masterKey,
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+ )
+
+ // 生成新的会话ID
+ if (currentSessionId.isEmpty()) {
+ currentSessionId = java.util.UUID.randomUUID().toString()
+ }
+
+ // 保存密钥和会话ID
+ sharedPreferences.edit()
+ .putString(KEY_AZURE_SPEECH_KEY, azureSpeechKey)
+ .putString(KEY_AZURE_SPEECH_REGION, azureSpeechRegion)
+ .putString(KEY_VOLCANO_AI_API_KEY, volcanoAiApiKey)
+ .putString(KEY_SESSION_ID, currentSessionId)
+ .apply()
+
+ FileLogger.d("MainActivity", "密钥已安全保存到加密存储中")
+ } catch (e: Exception) {
+ FileLogger.e("MainActivity", "保存密钥到加密存储失败: ${e.message}")
+ e.printStackTrace()
+ }
+ }
+
+ /**
+ * 从 EncryptedSharedPreferences 获取密钥
+ */
+ fun loadKeysFromSecureStorage(context: Context): Boolean {
+ try {
+ // 创建或获取主密钥
+ val masterKey = MasterKey.Builder(context)
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
+ .build()
+
+ // 获取 EncryptedSharedPreferences
+ val sharedPreferences = EncryptedSharedPreferences.create(
+ context,
+ SECURE_PREFS_FILENAME,
+ masterKey,
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+ )
+
+ // 检查会话ID是否匹配
+ val savedSessionId = sharedPreferences.getString(KEY_SESSION_ID, "") ?: ""
+
+ // 如果会话ID为空或不匹配,说明是新会话或应用被重启
+ if (savedSessionId.isEmpty() || currentSessionId.isEmpty() || savedSessionId != currentSessionId) {
+ // 会话不匹配,但尝试加载密钥,不返回错误
+ FileLogger.d("MainActivity", "会话ID不匹配,但尝试加载密钥")
+ // 设置当前会话ID为保存的会话ID
+ currentSessionId = savedSessionId
+ }
+
+ // 加载密钥
+ azureSpeechKey = sharedPreferences.getString(KEY_AZURE_SPEECH_KEY, "") ?: ""
+ azureSpeechRegion = sharedPreferences.getString(KEY_AZURE_SPEECH_REGION, "") ?: ""
+ volcanoAiApiKey = sharedPreferences.getString(KEY_VOLCANO_AI_API_KEY, "") ?: ""
+
+ FileLogger.d("MainActivity", "已从加密存储加载密钥")
+
+ // 检查是否成功获取所有密钥
+ return azureSpeechKey.isNotEmpty() && azureSpeechRegion.isNotEmpty() && volcanoAiApiKey.isNotEmpty()
+ } catch (e: Exception) {
+ FileLogger.e("MainActivity", "从加密存储加载密钥失败: ${e.message}")
+ e.printStackTrace()
+ return false
+ }
+ }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- // 初始化本地的 Azure 语音服务实例
- Log.d(TAG, "初始化本地的 Azure 语音服务实例")
- azureAsrHelper = AzureAsrHelper()
- azureTtsHelper = AzureTtsHelper()
+ // 初始化 FileLogger
+ FileLogger.init(applicationContext)
+
+ // 初始化 Azure 语音服务
+ azureAsrHelper = AzureAsrHelper(applicationContext)
+ azureTtsHelper = AzureTtsHelper(applicationContext)
classicBluetoothHelper = ClassicBluetoothHelper(applicationContext)
// 注册广播接收器
@@ -191,6 +291,10 @@ class MainActivity: FlutterActivity() {
// 设置方法通道
setupMethodChannels(flutterEngine)
+ // 初始化 Azure 语音服务
+ azureAsrHelper = AzureAsrHelper(this)
+ azureTtsHelper = AzureTtsHelper(this)
+
Log.d(TAG, "Flutter 引擎配置完成")
}
@@ -268,8 +372,6 @@ class MainActivity: FlutterActivity() {
"initialize" -> {
val subscriptionKey = call.argument("subscriptionKey")
val region = call.argument("region")
- val language = call.argument("language") ?: "zh-CN"
- val autoDetectLanguage = call.argument("autoDetectLanguage") ?: false
val supportedLanguages = call.argument>("supportedLanguages")?.toTypedArray() ?: arrayOf("zh-CN", "en-US")
if (subscriptionKey == null || region == null) {
@@ -285,9 +387,8 @@ class MainActivity: FlutterActivity() {
}
}
"recognizeOnce" -> {
- val language = call.argument("language")
- azureAsrHelper.recognizeOnce(language, object : AzureAsrHelper.RecognizeCallback {
+ azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {
result.success(mapOf(
"text" to text,
@@ -375,37 +476,6 @@ class MainActivity: FlutterActivity() {
azureAsrHelper.dispose()
result.success(true)
}
- "enableAutoDetectLanguage" -> {
- val enable = call.argument("enable") ?: false
- val languages = call.argument>("languages")?.toTypedArray()
-
- try {
- val success = azureAsrHelper.enableAutoDetectLanguage(enable, languages)
- result.success(success)
- } catch (e: Exception) {
- result.error("AUTO_DETECT_ERROR", e.message, null)
- }
- }
-
- "setSupportedLanguages" -> {
- val languages = call.argument>("languages")?.toTypedArray()
-
- if (languages == null || languages.isEmpty()) {
- result.error("INVALID_ARGUMENTS", "语言列表不能为空", null)
- return@setMethodCallHandler
- }
-
- try {
- azureAsrHelper.setSupportedLanguages(languages)
- result.success(true)
- } catch (e: Exception) {
- result.error("SET_LANGUAGES_ERROR", e.message, null)
- }
- }
-
- "isAutoDetectLanguageEnabled" -> {
- result.success(azureAsrHelper.isAutoDetectLanguageEnabled())
- }
else -> {
result.notImplemented()
}
@@ -416,21 +486,12 @@ class MainActivity: FlutterActivity() {
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_TTS_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"initialize" -> {
- val subscriptionKey = call.argument("subscriptionKey")
- val region = call.argument("serviceRegion")
+ val subscriptionKey = call.argument("subscriptionKey") ?: ""
+ val region = call.argument("region") ?: ""
val language = call.argument("language") ?: "zh-CN"
- if (subscriptionKey == null || region == null) {
- result.error("INVALID_ARGUMENTS", "订阅密钥和区域不能为空", null)
- return@setMethodCallHandler
- }
-
- try {
- val success = azureTtsHelper.initialize(subscriptionKey, region, language, this)
- result.success(success)
- } catch (e: Exception) {
- result.error("INITIALIZATION_ERROR", e.message, null)
- }
+ val success = azureTtsHelper.initialize(subscriptionKey, region, language)
+ result.success(success)
}
"setVoice" -> {
val voiceName = call.argument("voiceName") ?: return@setMethodCallHandler result.error("INVALID_ARGUMENTS", "语音名称不能为空", null)
@@ -601,7 +662,7 @@ class MainActivity: FlutterActivity() {
* 启动语音交互服务(带配置参数)
*/
private fun startVoiceInteractionService(call: MethodCall, result: MethodChannel.Result) {
- Log.d(TAG, "启动语音交互服务")
+ FileLogger.d(TAG, "启动语音交互服务")
// 获取配置参数
val key = call.argument("azure_speech_key") ?: ""
@@ -613,6 +674,9 @@ class MainActivity: FlutterActivity() {
azureSpeechRegion = region
volcanoAiApiKey = aiKey
+ // 保存密钥到安全存储
+ saveKeysToSecureStorage(applicationContext)
+
// 创建 Intent 并启动服务
val serviceIntent = Intent(this, VoiceInteractionService::class.java)
@@ -623,6 +687,7 @@ class MainActivity: FlutterActivity() {
startService(serviceIntent)
}
+ // 返回成功结果
result.success(true)
}
@@ -716,6 +781,7 @@ class MainActivity: FlutterActivity() {
}
override fun onDestroy() {
+ Log.d(TAG, "onDestroy")
// 注销所有广播接收器
try {
unregisterReceiver(voiceInteractionReceiver)
diff --git a/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt b/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt
index a56c872cc..978f97e9c 100644
--- a/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt
+++ b/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt
@@ -25,6 +25,7 @@ import org.json.JSONArray
import android.media.MediaPlayer
import android.media.AudioAttributes
import android.net.Uri
+import com.example.deep_voice.core.utils.FileLogger
/**
* 后台语音交互 Service:
@@ -70,6 +71,8 @@ class VoiceInteractionService : Service() {
// 当前用户输入
private var currentUserInput = ""
+
+ private var testKey = ""
// 服务组件
private lateinit var mediaSession: MediaSessionCompat
@@ -96,12 +99,14 @@ class VoiceInteractionService : Service() {
用户用语音和你交互.
""".trimIndent()
- // 添加无声媒体播放器
- private var silentPlayer: SilentMediaPlayer? = null
+ // 添加媒体播放器
+ private var audioPlayer: MiniMediaPlayer? = null
override fun onCreate() {
super.onCreate()
- Log.d(TAG, "onCreate - 语音交互服务正在创建")
+ // 初始化文件日志系统
+ FileLogger.init(applicationContext)
+ FileLogger.d(TAG, "onCreate - 语音交互服务正在创建")
// 初始化状态
isRunning.set(true)
@@ -109,31 +114,25 @@ class VoiceInteractionService : Service() {
// 初始化组件
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
- Log.d(TAG, "AudioManager初始化完成")
+ FileLogger.d(TAG, "AudioManager初始化完成")
initServices()
initMediaSession()
registerMediaButtonReceiver()
-
- silentPlayer = SilentMediaPlayer(applicationContext)
- silentPlayer?.start()
- // Log.d(TAG, "无声播放器已启动")
-
+ // 创建音频播放器
+ audioPlayer = MiniMediaPlayer(applicationContext)
+ audioPlayer?.play(R.raw.silence, 0f)
+
// 设置为媒体播放状态
- setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
- Log.d(TAG, "设置播放状态为PLAYING")
+ // setPlaybackState(PlaybackStateCompat.STATE_PAUSED)
+ // FileLogger.d(TAG, "设置播放状态为STATE_PAUSED")
// 启动监控和前台服务
startMonitoring()
startForegroundServiceWithNotification()
- Log.d(TAG, "VoiceInteractionService 初始化完成,等待蓝牙按键事件...")
-
- // 延迟3秒后测试按键处理功能
- // Handler(Looper.getMainLooper()).postDelayed({
- // testMediaButtonEvent()
- // }, 3000)
+ FileLogger.d(TAG, "VoiceInteractionService 初始化完成,等待蓝牙按键事件...")
}
/**
@@ -152,31 +151,56 @@ class VoiceInteractionService : Service() {
*/
private fun initServices() {
// 创建新的Azure服务实例
- Log.d(TAG, "创建新的Azure服务实例")
- azureAsrHelper = AzureAsrHelper()
- azureTtsHelper = AzureTtsHelper()
-
- // 从 MainActivity 获取 Azure 配置
- val subscriptionKey = MainActivity.azureSpeechKey
- val serviceRegion = MainActivity.azureSpeechRegion
+ FileLogger.d(TAG, "创建新的Azure服务实例")
+ azureAsrHelper = AzureAsrHelper(this)
+ azureTtsHelper = AzureTtsHelper(this)
+
+ // 尝试从静态变量获取配置
+ var subscriptionKey = MainActivity.azureSpeechKey
+ var serviceRegion = MainActivity.azureSpeechRegion
+ var volcanoKey = MainActivity.volcanoAiApiKey
+
+ // 如果静态变量中没有配置,尝试从加密存储中加载
+ if (subscriptionKey.isEmpty() || serviceRegion.isEmpty() || volcanoKey.isEmpty()) {
+ FileLogger.d(TAG, "静态变量中的配置信息不完整,尝试从加密存储加载")
+
+ // 从加密存储加载密钥
+ val keysLoaded = MainActivity.loadKeysFromSecureStorage(applicationContext)
+
+ if (keysLoaded) {
+ // 更新本地变量
+ subscriptionKey = MainActivity.azureSpeechKey
+ serviceRegion = MainActivity.azureSpeechRegion
+ volcanoKey = MainActivity.volcanoAiApiKey
+
+ FileLogger.d(TAG, "已从加密存储加载配置信息")
+ } else {
+ FileLogger.e(TAG, "无法从加密存储加载配置信息")
+ }
+ }
+ // 初始化语音服务
if (subscriptionKey.isNotEmpty() && serviceRegion.isNotEmpty()) {
// 初始化ASR
azureAsrHelper.initialize(subscriptionKey, serviceRegion, arrayOf("zh-CN"))
+
// 初始化TTS
azureTtsHelper.initialize(subscriptionKey, serviceRegion, "zh-CN")
+
+ FileLogger.d(TAG, "Azure语音服务已初始化")
} else {
- Log.e(TAG, "Azure配置信息不完整,无法初始化Azure服务")
+ FileLogger.e(TAG, "Azure配置信息不完整,无法初始化Azure服务")
}
// 初始化火山AI服务
volcanoAIService = VolcanoAIService()
- // 使用 MainActivity 中的 API key 初始化
- val volcanoApiKey = MainActivity.volcanoAiApiKey
- if (volcanoApiKey.isNotEmpty()) {
- volcanoAIService.initialize(volcanoApiKey)
+
+ // 初始化火山AI服务
+ if (volcanoKey.isNotEmpty()) {
+ volcanoAIService.initialize(volcanoKey)
+ FileLogger.d(TAG, "火山AI服务已初始化")
} else {
- Log.e(TAG, "火山AI配置信息不完整,无法初始化火山AI服务")
+ FileLogger.e(TAG, "火山AI配置信息不完整,无法初始化火山AI服务")
}
}
@@ -184,7 +208,7 @@ class VoiceInteractionService : Service() {
* 初始化 MediaSession
*/
private fun initMediaSession() {
- Log.d(TAG, "开始初始化MediaSession")
+ FileLogger.d(TAG, "开始初始化MediaSession")
mediaSession = MediaSessionCompat(applicationContext, "VoiceServiceSession")
// 设置标志和状态
@@ -192,91 +216,24 @@ class VoiceInteractionService : Service() {
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
- Log.d(TAG, "MediaSession标志设置完成")
+ FileLogger.d(TAG, "MediaSession标志设置完成")
// 设置回调
mediaSession.setCallback(object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
- Log.d(TAG, "收到媒体按钮事件: $mediaButtonEvent")
+ FileLogger.d(TAG, "initMediaSession收到媒体按钮事件: $mediaButtonEvent")
handleDoubleClick()
- return true
-
- val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
- } else {
- @Suppress("DEPRECATION")
- mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) as? KeyEvent
- }
-
- if (keyEvent != null) {
- Log.d(TAG, "解析到按键事件: action=${keyEvent.action}, keyCode=${keyEvent.keyCode}")
-
- if (keyEvent.action == KeyEvent.ACTION_DOWN) {
- when (keyEvent.keyCode) {
- KeyEvent.KEYCODE_MEDIA_PLAY -> {
- Log.d(TAG, "检测到MEDIA_PLAY按键")
- handleDoubleClick()
- return true
- }
- KeyEvent.KEYCODE_MEDIA_PAUSE -> {
- Log.d(TAG, "检测到MEDIA_PAUSE按键")
- handleDoubleClick()
- return true
- }
- KeyEvent.KEYCODE_HEADSETHOOK -> {
- Log.d(TAG, "检测到HEADSETHOOK按键")
- // 检测双击
- val currentTime = System.currentTimeMillis()
- val timeDiff = currentTime - lastKeyEventTime
-
- Log.d(TAG, "按键时间差: $timeDiff ms, 当前计数: $keyEventCount")
-
- if (timeDiff < 500) { // 500毫秒内的按键视为连续按键
- keyEventCount++
- Log.d(TAG, "连续按键,计数增加到: $keyEventCount")
- if (keyEventCount == 1) { // 这是第二次按键,即双击
- Log.d(TAG, "检测到双击,执行处理")
- handleDoubleClick()
- keyEventCount = 0
- return true
- }
- } else {
- Log.d(TAG, "非连续按键,重置计数")
- keyEventCount = 0
- }
- lastKeyEventTime = currentTime
-
- // 单击也处理
- Log.d(TAG, "单击也处理")
- handleDoubleClick()
- return true
- }
- KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
- Log.d(TAG, "检测到MEDIA_PLAY_PAUSE按键")
- handleDoubleClick()
- return true
- }
- else -> {
- Log.d(TAG, "检测到其他按键: ${keyEvent.keyCode}")
- }
- }
- } else {
- Log.d(TAG, "非ACTION_DOWN事件: ${keyEvent.action}")
- }
- } else {
- Log.d(TAG, "Intent中没有KeyEvent")
- }
- return super.onMediaButtonEvent(mediaButtonEvent)
+ return true
}
override fun onPlay() {
- Log.d(TAG, "收到onPlay回调")
+ FileLogger.d(TAG, "收到onPlay回调")
handleDoubleClick()
return super.onPlay()
}
override fun onPause() {
- Log.d(TAG, "收到onPause回调")
+ FileLogger.d(TAG, "收到onPause回调")
handleDoubleClick()
return super.onPause()
}
@@ -284,7 +241,7 @@ class VoiceInteractionService : Service() {
// 激活 MediaSession
mediaSession.isActive = true
- Log.d(TAG, "MediaSession已激活")
+ FileLogger.d(TAG, "MediaSession已激活")
}
/**
@@ -294,9 +251,9 @@ class VoiceInteractionService : Service() {
try {
val mediaButtonReceiver = ComponentName(packageName, MediaButtonReceiver::class.java.name)
audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver)
- Log.d(TAG, "媒体按钮接收器注册成功: $mediaButtonReceiver")
+ FileLogger.d(TAG, "媒体按钮接收器注册成功: $mediaButtonReceiver")
} catch (e: Exception) {
- Log.e(TAG, "媒体按钮接收器注册失败: ${e.message}", e)
+ FileLogger.e(TAG, "媒体按钮接收器注册失败: ${e.message}", e)
}
}
@@ -330,7 +287,7 @@ class VoiceInteractionService : Service() {
// 如果超过5秒没有检测到语音,且不在TTS播放中,暂停语音识别
if (!hasSpeechDetected && !isTtsSpeaking && elapsedTime >= RECOGNITION_TIMEOUT) {
- Log.d(TAG, "超过${RECOGNITION_TIMEOUT/1000}秒未检测到语音,停止识别")
+ FileLogger.d(TAG, "超过${RECOGNITION_TIMEOUT/1000}秒未检测到语音,停止识别")
isTimeoutPaused = true
playNotification("没有听到您说话,已暂停对话。双击耳机按钮可重新开始。")
@@ -348,13 +305,13 @@ class VoiceInteractionService : Service() {
* 处理双击事件
*/
private fun handleDoubleClick() {
- Log.d(TAG, "处理双击事件")
+ FileLogger.d(TAG, "处理双击事件")
//屏蔽短时间重复响应的问题
val currentTime = System.currentTimeMillis()
val timeDiff = currentTime - lastKeyEventTime
if (timeDiff < 500) {
- Log.d(TAG, "短时间重复响应,忽略")
+ FileLogger.d(TAG, "短时间重复响应,忽略")
return
}
lastKeyEventTime = currentTime
@@ -363,42 +320,28 @@ class VoiceInteractionService : Service() {
stopCurrentTTS()
// 播放提示音
- playPrompt("我在!")
+ // playPrompt("我在!")
+ audioPlayer?.play(R.raw.listening)
// 设置为播放状态
- setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
+ // setPlaybackState(PlaybackStateCompat.STATE_PAUSED)
// 重置超时暂停标志
isTimeoutPaused = false
// 启动或重置语音识别
if (!isRecognitionActive) {
- Log.d(TAG, "语音识别未激活,开始启动")
+ FileLogger.d(TAG, "语音识别未激活,开始启动")
// 如果之前是因为超时暂停,重新初始化语音识别组件
- if (isTimeoutPaused) {
- Log.d(TAG, "之前因超时暂停,重新初始化Azure服务")
- // 释放旧的实例
- try {
- azureAsrHelper.dispose()
- } catch (e: Exception) {
- Log.e(TAG, "释放旧的Azure ASR实例失败: ${e.message}", e)
- }
-
- // 创建新的实例
- azureAsrHelper = AzureAsrHelper()
-
- // 重新初始化
- val subscriptionKey = MainActivity.azureSpeechKey
- val serviceRegion = MainActivity.azureSpeechRegion
- if (subscriptionKey.isNotEmpty() && serviceRegion.isNotEmpty()) {
- azureAsrHelper.initialize(subscriptionKey, serviceRegion, arrayOf("zh-CN"))
- }
- }
+ // if (isTimeoutPaused) {
+ // Log.d(TAG, "之前因超时暂停,重新初始化Azure服务")
+ // restartAsr()
+ // }
startVoiceRecognition()
} else {
- Log.d(TAG, "语音识别已激活,更新活动时间")
+ FileLogger.d(TAG, "语音识别已激活,更新活动时间")
updateLastActivityTime()
hasSpeechDetected = false
}
@@ -415,7 +358,7 @@ class VoiceInteractionService : Service() {
playNotification("需要录音权限,请在设置中授予权限")
return
}
- // 通知 Flutter 语音识别已启动
+ // 通知 Flutter 语音识别已启动
notifyRecognitionStarted()
isActive = true
@@ -468,6 +411,7 @@ class VoiceInteractionService : Service() {
})
} catch (e: Exception) {
isRecognitionActive = false
+ FileLogger.e(TAG, "启动语音识别失败: ${e.message}", e)
playNotification("启动语音识别失败")
@@ -480,7 +424,7 @@ class VoiceInteractionService : Service() {
private fun stopVoiceRecognition() {
if (!isRecognitionActive) return
- Log.d(TAG, "停止语音识别")
+ FileLogger.d(TAG, "停止语音识别")
try {
azureAsrHelper.stopContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
@@ -489,19 +433,19 @@ class VoiceInteractionService : Service() {
override fun onSessionStarted() {}
override fun onSessionStopped() {
isRecognitionActive = false
- Log.d(TAG, "语音识别会话已停止")
+ FileLogger.d(TAG, "语音识别会话已停止")
}
override fun onCanceled(reason: String, errorDetails: String) {
isRecognitionActive = false
- Log.d(TAG, "语音识别已取消: $reason")
+ FileLogger.d(TAG, "语音识别已取消: $reason")
}
override fun onError(error: String) {
isRecognitionActive = false
- Log.e(TAG, "停止语音识别时出错: $error")
+ FileLogger.e(TAG, "停止语音识别时出错: $error")
}
})
} catch (e: Exception) {
- Log.e(TAG, "停止语音识别异常: ${e.message}", e)
+ FileLogger.e(TAG, "停止语音识别异常: ${e.message}", e)
// 确保状态一致性
isRecognitionActive = false
}
@@ -533,7 +477,7 @@ class VoiceInteractionService : Service() {
// 同步聊天记录到Flutter端
notifyChatHistoryUpdated("personal_assistant", text, response)
} catch (e: Exception) {
- Log.e(TAG, "AI处理出错: ${e.message}")
+ FileLogger.e(TAG, "AI处理出错: ${e.message}")
playNotification("AI处理出错")
}
}.start()
@@ -689,11 +633,11 @@ class VoiceInteractionService : Service() {
* 当 Service 被启动时调用
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- Log.d(TAG, "onStartCommand: intent=$intent, action=${intent?.action}")
+ FileLogger.d(TAG, "onStartCommand: intent=$intent, action=${intent?.action}")
// 处理媒体按键 Intent
if (intent?.action == Intent.ACTION_MEDIA_BUTTON) {
- Log.d(TAG, "收到媒体按钮Intent: ${intent.extras}")
+ FileLogger.d(TAG, "收到媒体按钮Intent: ${intent.extras}")
val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
@@ -703,26 +647,26 @@ class VoiceInteractionService : Service() {
}
if (keyEvent != null) {
- Log.d(TAG, "从Intent中解析到按键事件: action=${keyEvent.action}, keyCode=${keyEvent.keyCode}")
+ FileLogger.d(TAG, "从Intent中解析到按键事件: action=${keyEvent.action}, keyCode=${keyEvent.keyCode}")
handleKeyEvent(keyEvent)
} else {
- Log.d(TAG, "Intent中没有按键事件,直接处理双击")
+ FileLogger.d(TAG, "Intent中没有按键事件,直接处理双击")
handleDoubleClick()
}
} else if (intent?.action == ACTION_PAUSE_VOICE_INTERACTION) {
// 处理来自 Flutter 的暂停请求
- Log.d(TAG, "收到暂停请求")
+ FileLogger.d(TAG, "收到暂停请求")
pauseVoiceInteraction()
} else {
- Log.d(TAG, "收到其他Intent: action=${intent?.action}")
+ FileLogger.d(TAG, "收到其他Intent: action=${intent?.action}")
}
// 让 MediaSession 处理 Intent
try {
MediaButtonReceiver.handleIntent(mediaSession, intent)
- Log.d(TAG, "MediaButtonReceiver处理Intent完成")
+ FileLogger.d(TAG, "MediaButtonReceiver处理Intent完成")
} catch (e: Exception) {
- Log.e(TAG, "MediaButtonReceiver处理Intent失败: ${e.message}", e)
+ FileLogger.e(TAG, "MediaButtonReceiver处理Intent失败: ${e.message}", e)
}
return START_STICKY
@@ -732,50 +676,50 @@ class VoiceInteractionService : Service() {
* 处理按键事件
*/
private fun handleKeyEvent(keyEvent: KeyEvent) {
- Log.d(TAG, "处理按键事件: action=${keyEvent.action}, keyCode=${keyEvent.keyCode}")
+ FileLogger.d(TAG, "处理按键事件: action=${keyEvent.action}, keyCode=${keyEvent.keyCode}")
if (keyEvent.action == KeyEvent.ACTION_DOWN) {
when (keyEvent.keyCode) {
KeyEvent.KEYCODE_MEDIA_PLAY -> {
- Log.d(TAG, "处理MEDIA_PLAY按键")
+ FileLogger.d(TAG, "处理MEDIA_PLAY按键")
handleDoubleClick()
}
KeyEvent.KEYCODE_MEDIA_PAUSE -> {
- Log.d(TAG, "处理MEDIA_PAUSE按键")
+ FileLogger.d(TAG, "处理MEDIA_PAUSE按键")
handleDoubleClick()
}
KeyEvent.KEYCODE_HEADSETHOOK -> {
- Log.d(TAG, "处理HEADSETHOOK按键")
+ FileLogger.d(TAG, "处理HEADSETHOOK按键")
// 检测双击
val currentTime = System.currentTimeMillis()
val timeDiff = currentTime - lastKeyEventTime
- Log.d(TAG, "按键时间差: $timeDiff ms, 当前计数: $keyEventCount")
+ FileLogger.d(TAG, "按键时间差: $timeDiff ms, 当前计数: $keyEventCount")
if (timeDiff < 500) { // 500毫秒内的按键视为连续按键
keyEventCount++
- Log.d(TAG, "连续按键,计数增加到: $keyEventCount")
+ FileLogger.d(TAG, "连续按键,计数增加到: $keyEventCount")
if (keyEventCount == 1) { // 这是第二次按键,即双击
- Log.d(TAG, "检测到双击,执行处理")
+ FileLogger.d(TAG, "检测到双击,执行处理")
handleDoubleClick()
keyEventCount = 0
}
} else {
- Log.d(TAG, "非连续按键,重置计数")
+ FileLogger.d(TAG, "非连续按键,重置计数")
keyEventCount = 0
}
lastKeyEventTime = currentTime
}
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
- Log.d(TAG, "处理MEDIA_PLAY_PAUSE按键")
+ FileLogger.d(TAG, "处理MEDIA_PLAY_PAUSE按键")
handleDoubleClick()
}
else -> {
- Log.d(TAG, "处理其他按键: ${keyEvent.keyCode}")
+ FileLogger.d(TAG, "处理其他按键: ${keyEvent.keyCode}")
}
}
} else {
- Log.d(TAG, "非ACTION_DOWN事件: ${keyEvent.action}")
+ FileLogger.d(TAG, "非ACTION_DOWN事件: ${keyEvent.action}")
}
}
@@ -783,11 +727,11 @@ class VoiceInteractionService : Service() {
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
- Log.d(TAG, "onDestroy - 语音交互服务正在销毁")
+ FileLogger.d(TAG, "onDestroy - 语音交互服务正在销毁")
- // 停止无声播放器
- silentPlayer?.release()
- silentPlayer = null
+ // 释放音频播放器
+ audioPlayer?.release()
+ audioPlayer = null
// 停止监控
stopMonitoring()
@@ -804,13 +748,16 @@ class VoiceInteractionService : Service() {
mediaSession.release()
// 释放Azure服务实例
- Log.d(TAG, "释放Azure服务实例")
+ FileLogger.d(TAG, "释放Azure服务实例")
azureAsrHelper.dispose()
azureTtsHelper.dispose()
// 更新服务状态
isRunning.set(false)
+ // 关闭日志系统
+ FileLogger.shutdown()
+
super.onDestroy()
}
@@ -819,7 +766,7 @@ class VoiceInteractionService : Service() {
* 用于从 Flutter 端控制服务
*/
private fun pauseVoiceInteraction() {
- Log.d(TAG, "暂停语音交互(来自Flutter的请求)")
+ FileLogger.d(TAG, "暂停语音交互(来自Flutter的请求)")
// 停止当前TTS播放
stopCurrentTTS()
@@ -838,9 +785,9 @@ class VoiceInteractionService : Service() {
* 通知 Flutter 端聊天记录已更新
*/
private fun notifyChatHistoryUpdated(agentId: String, userMessage: String, assistantMessage: String) {
- Log.d(TAG, "通知Flutter聊天记录已更新: agentId=$agentId")
- Log.d(TAG, "用户消息: ${userMessage.take(50)}...")
- Log.d(TAG, "助手回复: ${assistantMessage.take(50)}...")
+ FileLogger.d(TAG, "通知Flutter聊天记录已更新: agentId=$agentId")
+ FileLogger.d(TAG, "用户消息: ${userMessage.take(50)}...")
+ FileLogger.d(TAG, "助手回复: ${assistantMessage.take(50)}...")
// 创建广播 Intent
val intent = Intent(ACTION_CHAT_HISTORY_UPDATED).apply {
@@ -852,14 +799,14 @@ class VoiceInteractionService : Service() {
// 发送广播
sendBroadcast(intent)
- Log.d(TAG, "已发送聊天记录广播")
+ FileLogger.d(TAG, "已发送聊天记录广播")
}
/**
* 通知 Flutter 端语音识别已启动
*/
private fun notifyRecognitionStarted() {
- Log.d(TAG, "通知Flutter语音识别已启动")
+ FileLogger.d(TAG, "通知Flutter语音识别已启动")
// 创建广播 Intent
val intent = Intent(ACTION_RECOGNITION_STARTED).apply {
@@ -871,162 +818,77 @@ class VoiceInteractionService : Service() {
sendBroadcast(intent)
}
- /**
- * 测试媒体按钮事件处理
- */
- private fun testMediaButtonEvent() {
- Log.d(TAG, "开始测试媒体按钮事件处理")
-
- try {
- // 创建一个模拟的HEADSETHOOK按键事件
- val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK)
-
- // 创建一个包含按键事件的Intent
- val intent = Intent(Intent.ACTION_MEDIA_BUTTON).apply {
- putExtra(Intent.EXTRA_KEY_EVENT, keyEvent)
- }
-
- // 直接调用onMediaButtonEvent方法
- Log.d(TAG, "直接调用onMediaButtonEvent方法")
- val result = mediaSession.controller.dispatchMediaButtonEvent(keyEvent)
- Log.d(TAG, "dispatchMediaButtonEvent结果: $result")
-
- // 通过MediaButtonReceiver发送Intent
- Log.d(TAG, "通过MediaButtonReceiver发送Intent")
- MediaButtonReceiver.handleIntent(mediaSession, intent)
-
- // 直接调用handleKeyEvent方法
- Log.d(TAG, "直接调用handleKeyEvent方法")
- handleKeyEvent(keyEvent)
-
- Log.d(TAG, "测试媒体按钮事件处理完成")
- } catch (e: Exception) {
- Log.e(TAG, "测试媒体按钮事件处理失败: ${e.message}", e)
- }
- }
/**
- * 无声媒体播放器类
- * 用于播放一个1秒的静音音频,确保系统认为app在实际播放声音
+ * 简易媒体播放器,用于播放raw中的音频资源
*/
- inner class SilentMediaPlayer(private val context: Context) {
+ inner class MiniMediaPlayer(private val context: Context) {
private var mediaPlayer: MediaPlayer? = null
- private var isPlayingAudio = false
-
- init {
- try {
- setupMediaPlayer()
- } catch (e: Exception) {
- Log.e(TAG, "初始化无声播放器失败: ${e.message}", e)
- }
- }
- private fun setupMediaPlayer() {
+ /**
+ * 播放音频资源
+ * @param resId 资源ID (R.raw.xxx)
+ * @param volume 音量 0.0-1.0
+ */
+ fun play(resId: Int, volume: Float = 1.0f) {
try {
- // 创建一个MediaPlayer
- mediaPlayer = MediaPlayer()
-
- // 设置一个自带的静音资源
- val afd = context.resources.openRawResourceFd(R.raw.silence)
- if (afd != null) {
- mediaPlayer?.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
- afd.close()
- } else {
- // 如果没有资源,尝试使用一个虚拟数据源
- Log.w(TAG, "未找到静音资源,尝试使用虚拟数据源")
- mediaPlayer?.setDataSource(context, Uri.parse("android.resource://${context.packageName}/raw/silence"))
- }
-
- // 设置循环播放
- mediaPlayer?.isLooping = false
-
- // 设置音量为0
- mediaPlayer?.setVolume(0f, 0f)
+ // 释放之前的资源
+ release()
- // 设置音频特性
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- val audioAttributes = AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_MEDIA)
- .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
- .build()
- mediaPlayer?.setAudioAttributes(audioAttributes)
- } else {
- @Suppress("DEPRECATION")
- mediaPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC)
- }
-
- // 准备播放
- mediaPlayer?.prepare()
-
- // 设置完成监听器 - 虽然设置了循环播放,但以防万一
- mediaPlayer?.setOnCompletionListener {
- Log.d(TAG, "无声播放完成")
- isPlayingAudio = false
- release()
- // start()
- }
-
- // 设置错误监听器
- mediaPlayer?.setOnErrorListener { _, what, extra ->
- Log.e(TAG, "无声播放错误: what=$what, extra=$extra")
- isPlayingAudio = false
- // 尝试重新创建
- setupMediaPlayer()
- true
- }
-
- Log.d(TAG, "无声播放器初始化成功")
- } catch (e: Exception) {
- Log.e(TAG, "设置无声播放器失败: ${e.message}", e)
- mediaPlayer?.release()
- mediaPlayer = null
- }
- }
-
- fun start() {
- try {
- if (mediaPlayer == null) {
- setupMediaPlayer()
- }
-
- if (mediaPlayer != null && !isPlayingAudio) {
- mediaPlayer?.start()
- isPlayingAudio = true
- Log.d(TAG, "无声播放开始")
+ // 创建播放器并设置资源
+ mediaPlayer = MediaPlayer().apply {
+ // 设置音频资源
+ context.resources.openRawResourceFd(resId)?.use { fd ->
+ setDataSource(fd.fileDescriptor, fd.startOffset, fd.length)
+ }
+
+ // 设置音量
+ setVolume(volume.coerceIn(0f, 1f), volume.coerceIn(0f, 1f))
+
+ // 播放完成后自动释放
+ setOnCompletionListener { release() }
+
+ // 准备并播放
+ prepare()
+ start()
}
} catch (e: Exception) {
- Log.e(TAG, "开始无声播放失败: ${e.message}", e)
-
+ release()
}
}
+ /**
+ * 停止播放
+ */
fun stop() {
try {
- if (mediaPlayer != null && isPlayingAudio) {
- mediaPlayer?.pause()
- isPlayingAudio = false
- Log.d(TAG, "无声播放停止")
+ mediaPlayer?.apply {
+ if (isPlaying) stop()
}
} catch (e: Exception) {
- Log.e(TAG, "停止无声播放失败: ${e.message}", e)
+ // 忽略异常
}
}
+ /**
+ * 释放资源
+ */
fun release() {
try {
- mediaPlayer?.stop()
- mediaPlayer?.release()
+ mediaPlayer?.apply {
+ if (isPlaying) stop()
+ release()
+ }
mediaPlayer = null
- isPlayingAudio = false
- Log.d(TAG, "无声播放器已释放")
} catch (e: Exception) {
- Log.e(TAG, "释放无声播放器失败: ${e.message}", e)
+ // 忽略异常
}
}
- fun isPlaying(): Boolean {
- return isPlayingAudio && mediaPlayer != null
- }
+ /**
+ * 是否正在播放
+ */
+ fun isPlaying() = mediaPlayer?.isPlaying == true
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/example/deep_voice/core/utils/FileLogger.kt b/android/app/src/main/kotlin/com/example/deep_voice/core/utils/FileLogger.kt
new file mode 100644
index 000000000..9d32a81b8
--- /dev/null
+++ b/android/app/src/main/kotlin/com/example/deep_voice/core/utils/FileLogger.kt
@@ -0,0 +1,237 @@
+package com.example.deep_voice.core.utils
+
+import android.content.Context
+import android.util.Log
+import java.io.File
+import java.io.FileWriter
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.*
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+/**
+ * 文件日志工具
+ * 将日志同时输出到Logcat和文件系统
+ */
+class FileLogger private constructor(context: Context) {
+ private val logDir: File
+ private var currentLogFile: File? = null
+ private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ private val timeFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())
+ private val logExecutor: ExecutorService = Executors.newSingleThreadExecutor()
+
+ init {
+ // 初始化日志目录
+ logDir = File(context.getExternalFilesDir(null), "logs")
+ if (!logDir.exists()) {
+ logDir.mkdirs()
+ }
+
+ // 创建当天的日志文件
+ updateLogFile()
+ }
+
+ /**
+ * 更新日志文件为当天日期
+ */
+ private fun updateLogFile() {
+ val today = dateFormat.format(Date())
+ val logFile = File(logDir, "log_$today.txt")
+
+ if (currentLogFile == null || currentLogFile?.absolutePath != logFile.absolutePath) {
+ currentLogFile = logFile
+
+ // 如果是新文件,添加文件头
+ if (!logFile.exists()) {
+ try {
+ logFile.createNewFile()
+ FileWriter(logFile, true).use { writer ->
+ writer.append("===== 日志开始: ${SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())} =====\n")
+ }
+ } catch (e: IOException) {
+ Log.e(TAG, "创建日志文件失败: ${e.message}")
+ }
+ }
+ }
+ }
+
+ /**
+ * 调试级别日志
+ */
+ fun d(tag: String, message: String) {
+ // 输出到Logcat
+ Log.d(tag, message)
+
+ // 异步写入文件
+ logToFile("D", tag, message)
+ }
+
+ /**
+ * 信息级别日志
+ */
+ fun i(tag: String, message: String) {
+ // 输出到Logcat
+ Log.i(tag, message)
+
+ // 异步写入文件
+ logToFile("I", tag, message)
+ }
+
+ /**
+ * 警告级别日志
+ */
+ fun w(tag: String, message: String) {
+ // 输出到Logcat
+ Log.w(tag, message)
+
+ // 异步写入文件
+ logToFile("W", tag, message)
+ }
+
+ /**
+ * 错误级别日志
+ */
+ fun e(tag: String, message: String, throwable: Throwable? = null) {
+ // 输出到Logcat
+ if (throwable != null) {
+ Log.e(tag, message, throwable)
+ } else {
+ Log.e(tag, message)
+ }
+
+ // 异步写入文件
+ val errorMsg = if (throwable != null) {
+ "$message\n${throwable.message}\n${throwable.stackTraceToString()}"
+ } else {
+ message
+ }
+
+ logToFile("E", tag, errorMsg)
+ }
+
+ /**
+ * 将日志写入文件
+ */
+ private fun logToFile(level: String, tag: String, message: String) {
+ return
+ logExecutor.execute {
+ try {
+ // 检查是否需要更新日志文件
+ updateLogFile()
+
+ // 格式化日志内容
+ val timestamp = timeFormat.format(Date())
+ val logLine = "$timestamp $level/$tag: $message\n"
+
+ // 写入文件
+ if (currentLogFile != null && currentLogFile!!.exists()) {
+ FileWriter(currentLogFile!!, true).use { writer ->
+ writer.append(logLine)
+ }
+ }
+ } catch (e: IOException) {
+ Log.e(TAG, "写入日志文件失败: ${e.message}")
+ }
+ }
+ }
+
+ /**
+ * 清理旧日志文件
+ * @param daysToKeep 保留的天数
+ */
+ fun cleanOldLogs(daysToKeep: Int = 7) {
+ logExecutor.execute {
+ try {
+ val currentTime = System.currentTimeMillis()
+ val maxAge = daysToKeep * 24 * 60 * 60 * 1000L
+
+ logDir.listFiles()?.forEach { file ->
+ if (file.isFile && file.name.startsWith("log_") && file.name.endsWith(".txt")) {
+ val fileAge = currentTime - file.lastModified()
+ if (fileAge > maxAge) {
+ val deleted = file.delete()
+ if (deleted) {
+ Log.d(TAG, "已删除旧日志文件: ${file.name}")
+ }
+ }
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "清理旧日志失败: ${e.message}")
+ }
+ }
+ }
+
+ /**
+ * 关闭日志系统
+ */
+ fun shutdown() {
+ logExecutor.shutdown()
+ }
+
+ companion object {
+ private const val TAG = "FileLogger"
+ private var INSTANCE: FileLogger? = null
+
+ /**
+ * 初始化日志系统
+ */
+ @Synchronized
+ fun init(context: Context) {
+ if (INSTANCE == null) {
+ INSTANCE = FileLogger(context.applicationContext)
+ d(TAG, "文件日志系统已初始化")
+ }
+ }
+
+ /**
+ * 调试日志
+ */
+ fun d(tag: String, message: String) {
+ INSTANCE?.d(tag, message) ?: Log.d(tag, message)
+ }
+
+ /**
+ * 信息日志
+ */
+ fun i(tag: String, message: String) {
+ INSTANCE?.i(tag, message) ?: Log.i(tag, message)
+ }
+
+ /**
+ * 警告日志
+ */
+ fun w(tag: String, message: String) {
+ INSTANCE?.w(tag, message) ?: Log.w(tag, message)
+ }
+
+ /**
+ * 错误日志
+ */
+ fun e(tag: String, message: String, throwable: Throwable? = null) {
+ INSTANCE?.e(tag, message, throwable) ?: run {
+ if (throwable != null) {
+ Log.e(tag, message, throwable)
+ } else {
+ Log.e(tag, message)
+ }
+ }
+ }
+
+ /**
+ * 清理旧日志
+ */
+ fun cleanOldLogs(daysToKeep: Int = 7) {
+ INSTANCE?.cleanOldLogs(daysToKeep)
+ }
+
+ /**
+ * 关闭日志系统
+ */
+ fun shutdown() {
+ INSTANCE?.shutdown()
+ INSTANCE = null
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart
index 9327bdb58..e6ec09174 100644
--- a/lib/core/bindings/initial_binding.dart
+++ b/lib/core/bindings/initial_binding.dart
@@ -37,6 +37,6 @@ class InitialBinding extends Bindings {
Get.lazyPut(() => VoiceInteractionService(), fenix: true);
// 经典蓝牙服务
- Get.put(ClassicBluetoothService()..initialize(), permanent: true);
+ Get.lazyPut(() => ClassicBluetoothService(), fenix: true);
}
}
\ No newline at end of file
diff --git a/lib/data/services/azure_asr_service.dart b/lib/data/services/azure_asr_service.dart
index c24ebd480..e3ddb78d7 100644
--- a/lib/data/services/azure_asr_service.dart
+++ b/lib/data/services/azure_asr_service.dart
@@ -15,13 +15,7 @@ class AzureAsrService {
late final String _subscriptionKey;
late final String _serviceRegion;
- // 当前语言
- String _currentLanguage = 'zh-CN';
- String get currentLanguage => _currentLanguage;
-
- // 自动语言检测相关
- bool _isAutoDetectLanguageEnabled = false;
- bool get isAutoDetectLanguageEnabled => _isAutoDetectLanguageEnabled;
+
List _supportedLanguages = ['zh-CN', 'en-US'];
List get supportedLanguages => _supportedLanguages;
@@ -102,50 +96,35 @@ class AzureAsrService {
///
/// 返回 true 表示初始化成功,否则抛出 PlatformException
Future initialize({
- String language = 'zh-CN',
- bool autoDetectLanguage = false,
List? supportedLanguages,
}) async {
- _currentLanguage = language;
- _isAutoDetectLanguageEnabled = autoDetectLanguage;
-
- if (supportedLanguages != null && supportedLanguages.isNotEmpty) {
- _supportedLanguages = supportedLanguages;
- }
-
- if (_isInitialized &&
- _currentLanguage == language &&
- _isAutoDetectLanguageEnabled == autoDetectLanguage) {
- return true;
- }
-
- if (_isInitialized) {
- await dispose();
- }
-
try {
+
+ if (supportedLanguages != null && supportedLanguages.isNotEmpty) {
+ _supportedLanguages = supportedLanguages;
+ }
+
+ // 检查是否需要重新初始化
+ if (_isInitialized) {
+ await dispose();
+ }
+
final bool result = await _channel.invokeMethod('initialize', {
'subscriptionKey': _subscriptionKey,
'region': _serviceRegion,
- 'language': language,
- 'autoDetectLanguage': autoDetectLanguage,
'supportedLanguages': _supportedLanguages,
});
+
_isInitialized = result;
+ Logger.info('Azure 语音识别服务初始化${result ? '成功' : '失败'}');
return result;
} catch (e) {
- Logger.error('语音识别初始化失败: $e');
+ Logger.error('Azure 语音识别服务初始化失败: $e');
_isInitialized = false;
- throw e;
+ rethrow;
}
}
- /// 设置识别语言
- ///
- /// 注意:更改语言需要重新初始化识别器
- Future setRecognitionLanguage(String language) async {
- return initialize(language: language);
- }
/// 执行一次性语音识别
///
@@ -156,9 +135,7 @@ class AzureAsrService {
}
try {
- final result = await _channel.invokeMethod('recognizeOnce', {
- 'language': _currentLanguage,
- });
+ final result = await _channel.invokeMethod('recognizeOnce');
if (result is Map) {
final String text = result['text'] as String? ?? '';
@@ -175,7 +152,7 @@ class AzureAsrService {
throw Exception('无效的识别结果格式');
} catch (e) {
Logger.error('语音识别失败: $e');
- throw e;
+ rethrow;
}
}
@@ -187,6 +164,11 @@ class AzureAsrService {
await initialize();
}
+ // 确保事件通道已准备好
+ if (!await _waitForEventChannel()) {
+ throw Exception('事件通道未准备好,无法开始连续识别');
+ }
+
if (_isContinuousRecognitionActive) {
await stopContinuousRecognition();
}
@@ -194,6 +176,7 @@ class AzureAsrService {
try {
_eventStreamController = StreamController.broadcast();
+ // 开始连续识别
final bool result = await _channel.invokeMethod('startContinuousRecognition');
if (!result) {
@@ -206,7 +189,7 @@ class AzureAsrService {
} catch (e) {
Logger.error('开始连续语音识别失败: $e');
_cleanupEventStream();
- throw e;
+ rethrow;
}
}
@@ -225,7 +208,7 @@ class AzureAsrService {
} catch (e) {
Logger.error('停止连续语音识别失败: $e');
_cleanupEventStream();
- throw e;
+ rethrow;
}
}
@@ -324,68 +307,18 @@ class AzureAsrService {
await stopContinuousRecognition();
}
+ // 通知原生端释放资源
await _channel.invokeMethod('dispose');
_isInitialized = false;
+
+ Logger.info('Azure 语音识别资源已释放');
} catch (e) {
Logger.error('释放语音识别资源失败: $e');
_cleanupEventStream();
_isInitialized = false;
}
}
-
- /// 启用或禁用自动语言检测
- Future enableAutoDetectLanguage(bool enable, {List? languages}) async {
- if (!_isInitialized) {
- await initialize(autoDetectLanguage: enable, supportedLanguages: languages);
- return _isAutoDetectLanguageEnabled;
- }
-
- try {
- final bool result = await _channel.invokeMethod('enableAutoDetectLanguage', {
- 'enable': enable,
- 'languages': languages,
- });
-
- if (result) {
- _isAutoDetectLanguageEnabled = enable;
- if (languages != null && languages.isNotEmpty) {
- _supportedLanguages = languages;
- }
- }
-
- return result;
- } catch (e) {
- Logger.error('设置自动语言检测失败: $e');
- throw e;
- }
- }
-
- /// 设置支持的语言列表(用于自动语言检测)
- Future setSupportedLanguages(List languages) async {
- if (languages.isEmpty) {
- throw ArgumentError('语言列表不能为空');
- }
-
- if (!_isInitialized) {
- await initialize(supportedLanguages: languages);
- return true;
- }
-
- try {
- final bool result = await _channel.invokeMethod('setSupportedLanguages', {
- 'languages': languages,
- });
-
- if (result) {
- _supportedLanguages = languages;
- }
-
- return result;
- } catch (e) {
- Logger.error('设置支持的语言列表失败: $e');
- throw e;
- }
- }
+
}
/// 识别事件类型
diff --git a/lib/data/services/azure_tts_service.dart b/lib/data/services/azure_tts_service.dart
index 005dc9b5e..4cd000f22 100644
--- a/lib/data/services/azure_tts_service.dart
+++ b/lib/data/services/azure_tts_service.dart
@@ -69,7 +69,8 @@ class AzureTtsService extends GetxService {
_subscriptionKey = dotenv.env['AZURE_SPEECH_KEY'] ?? '';
_serviceRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? '';
}
-
+
+
/// 初始化TTS服务
Future initialize({String language = 'zh-CN'}) async {
if (_isInitialized) return true;
@@ -82,7 +83,7 @@ class AzureTtsService extends GetxService {
final result = await _channel.invokeMethod('initialize', {
'subscriptionKey': _subscriptionKey,
- 'serviceRegion': _serviceRegion,
+ 'region': _serviceRegion,
'language': language,
});
diff --git a/lib/data/services/classic_bluetooth_service.dart b/lib/data/services/classic_bluetooth_service.dart
index 39e77f4bf..2c62801a8 100644
--- a/lib/data/services/classic_bluetooth_service.dart
+++ b/lib/data/services/classic_bluetooth_service.dart
@@ -162,15 +162,7 @@ class ClassicBluetoothService extends GetxService {
try {
Logger.info('正在初始化 ClassicBluetoothService...');
- // 检查通道是否可用
- try {
- // 调用一个简单方法检查通道是否已设置
- await _channel.invokeMethod('isBluetoothEnabled');
- Logger.info('蓝牙方法通道可用');
- } catch (e) {
- Logger.error('蓝牙方法通道不可用: $e');
- // 这里不要立即返回false,尝试继续设置其他内容
- }
+
// 设置事件监听器
_setupBluetoothStateListener();
diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart
index da5222328..fd4dc4772 100644
--- a/lib/modules/chat/controllers/chat_controller.dart
+++ b/lib/modules/chat/controllers/chat_controller.dart
@@ -418,8 +418,6 @@ class ChatController extends GetxController {
_voiceInteractionService.pauseVoiceInteraction();
try {
- // 初始化语音识别
- await _asrService.initialize(language: 'zh-CN');
// 启动连续语音识别
// 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据
diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart
index 14318b870..0d71aa111 100644
--- a/lib/modules/pairing/controllers/pairing_controller.dart
+++ b/lib/modules/pairing/controllers/pairing_controller.dart
@@ -1,3 +1,4 @@
+import 'package:deep_voice/data/services/voice_interaction_service.dart';
import 'package:get/get.dart';
import 'dart:async';
import '../../../routes/app_routes.dart';
@@ -6,6 +7,7 @@ import '../../../core/utils/logger.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
+
/// 简化的配对控制器,仅用于显示已连接经典蓝牙设备和处理基本配对流程
class PairingController extends GetxController {
static PairingController get to => Get.find();
@@ -52,41 +54,17 @@ class PairingController extends GetxController {
_initialize();
}
- @override
- void onReady() {
- super.onReady();
- // 设置连接监听器,确保在页面完全加载后设置,避免过早监听可能导致的问题
- _setupDeviceListeners();
- }
-
- /// 设置设备监听器
- void _setupDeviceListeners() {
- try {
- if (!_isBluetoothServiceAvailable.value) return;
-
- // 监听设备连接事件
- _bluetoothService.onDeviceConnected((device) {
- Logger.info('检测到蓝牙设备连接: ${device.name}');
-
- // 更新状态并跳转到主页
- statusDescription.value = '已连接到设备: ${device.name}';
-
- // 立即跳转到主页
- _navigateToHome();
- });
- } catch (e) {
- Logger.error('设置设备监听器失败: $e');
- }
- }
-
- /// 立即跳转到主页
- void _navigateToHome() {
- Get.offAllNamed(Routes.home);
- }
-
+
/// 完整的初始化流程
Future _initialize() async {
try {
+
+ VoiceInteractionService.to.initialize();
+ VoiceInteractionService.to.startService();
+
+
+
+
isLoading.value = true;
statusDescription.value = '正在初始化...';
@@ -102,6 +80,9 @@ class PairingController extends GetxController {
return;
}
+ // 设置连接监听器,确保在页面完全加载后设置,避免过早监听可能导致的问题
+ _setupDeviceListeners();
+
// 2. 检查权限
statusDescription.value = '正在检查权限...';
final hasPermission = await _bluetoothService.checkAndRequestPermissions();
@@ -133,6 +114,33 @@ class PairingController extends GetxController {
isLoading.value = false;
}
}
+
+
+ /// 设置设备监听器
+ void _setupDeviceListeners() {
+ try {
+ if (!_isBluetoothServiceAvailable.value) return;
+
+ // 监听设备连接事件
+ _bluetoothService.onDeviceConnected((device) {
+ Logger.info('检测到蓝牙设备连接: ${device.name}');
+
+ // 更新状态并跳转到主页
+ statusDescription.value = '已连接到设备: ${device.name}';
+
+ // 立即跳转到主页
+ _navigateToHome();
+ });
+ } catch (e) {
+ Logger.error('设置设备监听器失败: $e');
+ }
+ }
+
+ /// 立即跳转到主页
+ void _navigateToHome() {
+ Get.offAllNamed(Routes.home);
+ }
+
/// 刷新设备
Future refreshConnectedDevices() async {
diff --git a/lib/modules/test/controllers/azure_asr_test_controller.dart b/lib/modules/test/controllers/azure_asr_test_controller.dart
index bcb390d49..ba60adcb1 100644
--- a/lib/modules/test/controllers/azure_asr_test_controller.dart
+++ b/lib/modules/test/controllers/azure_asr_test_controller.dart
@@ -54,9 +54,8 @@ class AzureAsrTestController extends GetxController {
// 初始化ASR服务
Future _initializeAsr() async {
try {
+ // 根据选择的支持语言初始化服务
final success = await _azureAsrService.initialize(
- language: selectedLanguage.value,
- autoDetectLanguage: isAutoDetectEnabled.value,
supportedLanguages: selectedSupportedLanguages,
);
asrState.value = success ? AzureAsrState.initialized : AzureAsrState.error;
@@ -214,16 +213,25 @@ class AzureAsrTestController extends GetxController {
selectedLanguage.value = language;
+ // 停止当前识别
if (_azureAsrService.isContinuousRecognitionActive()) {
await stopContinuousRecognition();
}
- final success = await _azureAsrService.setRecognitionLanguage(language);
- asrState.value = success ? AzureAsrState.initialized : AzureAsrState.error;
+ // 确保新语言在选中支持的语言列表中
+ if (!selectedSupportedLanguages.contains(language)) {
+ // 添加新语言到支持列表
+ final newLanguages = [...selectedSupportedLanguages, language];
+ selectedSupportedLanguages.value = newLanguages;
+
+ // 重新初始化ASR服务
+ await _initializeAsr();
+ }
recognizedText.value = '';
partialText.value = '';
detectedLanguage.value = '';
+ asrState.value = AzureAsrState.initialized;
} catch (e) {
Logger.error('更改语言出错: $e');
asrState.value = AzureAsrState.error;
@@ -235,16 +243,13 @@ class AzureAsrTestController extends GetxController {
try {
isAutoDetectEnabled.value = !isAutoDetectEnabled.value;
+ // 停止当前识别
if (_azureAsrService.isContinuousRecognitionActive()) {
await stopContinuousRecognition();
}
- final success = await _azureAsrService.enableAutoDetectLanguage(
- isAutoDetectEnabled.value,
- languages: selectedSupportedLanguages,
- );
-
- asrState.value = success ? AzureAsrState.initialized : AzureAsrState.error;
+ // 重新初始化ASR服务 - 自动语言检测现在通过选择多种语言实现
+ await _initializeAsr();
recognizedText.value = '';
partialText.value = '';
@@ -262,11 +267,16 @@ class AzureAsrTestController extends GetxController {
selectedSupportedLanguages.value = languages;
+ // 停止当前识别
if (_azureAsrService.isContinuousRecognitionActive()) {
await stopContinuousRecognition();
}
- final success = await _azureAsrService.setSupportedLanguages(languages);
+ // 重新初始化ASR服务,使用新的语言列表
+ final success = await _azureAsrService.initialize(
+ supportedLanguages: languages,
+ );
+
asrState.value = success ? AzureAsrState.initialized : AzureAsrState.error;
} catch (e) {
Logger.error('更新支持的语言列表出错: $e');
diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart
index 038b5104c..0fd6d268b 100644
--- a/lib/modules/translation/controllers/translation_controller.dart
+++ b/lib/modules/translation/controllers/translation_controller.dart
@@ -126,13 +126,17 @@ class TranslationController extends GetxController {
Future _initServices() async {
try {
await _translationService.initialize();
+ await _ttsService.initialize();
+ // 初始化 ASR 服务时,明确指定需要支持的语言
final List asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
- await _ttsService.initialize();
+ Logger.info('初始化ASR服务,支持语言: $asrSupportedLanguages');
+
await _asrService.initialize(
- autoDetectLanguage: true,
supportedLanguages: asrSupportedLanguages,
);
+
+ Logger.info('所有服务初始化完成');
} catch (e) {
Logger.error('初始化服务失败: $e');
}
@@ -158,9 +162,14 @@ class TranslationController extends GetxController {
_voiceInteractionService.pauseVoiceInteraction();
try {
- // 更新ASR支持的语言
- final List asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
- await _asrService.setSupportedLanguages(asrSupportedLanguages);
+ // 每次开始识别前检查 ASR 服务是否已配置为当前语言对
+ final List currentLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
+ final bool needsReinit = !_asrService.supportedLanguages.toSet().containsAll(currentLanguages);
+
+ if (needsReinit) {
+ Logger.info('识别开始前发现语言变更,重新初始化ASR服务');
+ await _reinitializeAsrService();
+ }
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString();
@@ -462,6 +471,9 @@ class TranslationController extends GetxController {
// 保存语言设置
_saveLanguageSettings();
+
+ // 更新ASR支持的语言并重新初始化
+ _reinitializeAsrService();
}
// 设置源语言
@@ -478,6 +490,9 @@ class TranslationController extends GetxController {
// 保存语言设置
_saveLanguageSettings();
+
+ // 更新ASR支持的语言并重新初始化
+ _reinitializeAsrService();
}
}
@@ -495,6 +510,28 @@ class TranslationController extends GetxController {
// 保存语言设置
_saveLanguageSettings();
+
+ // 更新ASR支持的语言并重新初始化
+ _reinitializeAsrService();
+ }
+ }
+
+ // 重新初始化ASR服务以支持新的语言
+ Future _reinitializeAsrService() async {
+ try {
+ // 更新ASR支持的语言
+ final List asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
+
+ Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages');
+
+ // 重新初始化ASR服务
+ await _asrService.initialize(
+ supportedLanguages: asrSupportedLanguages,
+ );
+
+ Logger.info('ASR服务重新初始化成功');
+ } catch (e) {
+ Logger.error('重新初始化ASR服务失败: $e');
}
}