Browse Source

安卓语言识别加入网络检测。

newdev_shunjiawei
fdp 1 year ago
parent
commit
70d9dd222d
  1. 12
      lib/modules/agent/controllers/agent_controller.dart
  2. 27
      lib/modules/opus_test/controllers/opus_test_controller.dart
  3. 38
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  4. 257
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

12
lib/modules/agent/controllers/agent_controller.dart

@ -649,11 +649,13 @@ class AgentController extends GetxController {
break; break;
case AgentServiceEventType.error: case AgentServiceEventType.error:
isListening.value = false; //验证错误码
isSpeaking.value = false; if (event.data['code'] == 1000) {
isProcessing.value = false; isListening.value = false;
isImageProcessing.value = false; isSpeaking.value = false;
isProcessing.value = false;
isImageProcessing.value = false;
}
// 移除临时的识别消息 // 移除临时的识别消息
final index = final index =
messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser); messages.lastIndexWhere((msg) => msg.isRecognizing && msg.isUser);

27
lib/modules/opus_test/controllers/opus_test_controller.dart

@ -103,16 +103,31 @@ class OpusTestController extends GetxController {
Future<void> loadExternalFiles() async { Future<void> loadExternalFiles() async {
try { try {
externalFiles.clear(); externalFiles.clear();
statusMessage.value = '正在扫描外部存储...'; statusMessage.value = '正在扫描存储...';
print('正在扫描存储...');
// 获取外部存储目录 List<Directory> directories = [];
final directories = await getExternalStorageDirectories();
if (directories == null || directories.isEmpty) { if (Platform.isAndroid) {
statusMessage.value = '未找到外部存储目录'; // Android: 使用外部存储目录
final androidDirs = await getExternalStorageDirectories();
if (androidDirs != null && androidDirs.isNotEmpty) {
directories.addAll(androidDirs);
}
} else if (Platform.isIOS) {
// iOS: 使用Documents目录
final iosDir = await getApplicationDocumentsDirectory();
directories.add(iosDir);
}
if (directories.isEmpty) {
statusMessage.value = '未找到存储目录';
return; return;
} }
// 遍历所有外部存储目录 print('存储目录: $directories');
// 遍历所有存储目录
for (final directory in directories) { for (final directory in directories) {
final dirPath = directory.path; final dirPath = directory.path;
final dir = Directory(dirPath); final dir = Directory(dirPath);

38
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -435,18 +435,7 @@ object AgentService : CoroutineScope {
Log.d(TAG, "通话模式:禁用空闲检测") Log.d(TAG, "通话模式:禁用空闲检测")
return return
} }
"ble_wakeup" -> {
// BLE唤醒模式:使用默认的空闲检测
startIdleCheck(maxIdleSeconds)
}
"push_to_talk" -> {
// 按住说话模式:使用较长的空闲检测时间
startIdleCheck(maxIdleSeconds)
}
"normal" -> {
// 普通模式:使用默认的空闲检测
startIdleCheck(maxIdleSeconds)
}
else -> { else -> {
// 未知模式:使用默认的空闲检测 // 未知模式:使用默认的空闲检测
Log.w(TAG, "未知识别模式: $mode,使用默认空闲检测") Log.w(TAG, "未知识别模式: $mode,使用默认空闲检测")
@ -543,9 +532,7 @@ object AgentService : CoroutineScope {
return false return false
} }
// 设置当前识别模式
currentRecognitionMode = mode
Log.d(TAG, "设置语音识别模式: $mode")
_isRecognitionActive.set(true) _isRecognitionActive.set(true)
_hasSpeechDetected.set(false) _hasSpeechDetected.set(false)
@ -561,7 +548,9 @@ object AgentService : CoroutineScope {
// 根据模式启动相应的空闲检测 // 根据模式启动相应的空闲检测
startIdleCheckForMode(mode) startIdleCheckForMode(mode)
// 设置当前识别模式
currentRecognitionMode = mode
Log.d(TAG, "设置语音识别模式: $mode")
return true return true
} catch (e: Exception) { } catch (e: Exception) {
_isRecognitionActive.set(false) _isRecognitionActive.set(false)
@ -584,12 +573,12 @@ object AgentService : CoroutineScope {
override fun onRecognizing(recognizing: String, detectedLanguage: String) { override fun onRecognizing(recognizing: String, detectedLanguage: String) {
if (recognizing.isNotEmpty()) { if (recognizing.isNotEmpty()) {
// 检测到语音,更新状态 // 检测到语音,更新状态
val previousHasSpeech = hasSpeechDetected
_hasSpeechDetected.set(true) _hasSpeechDetected.set(true)
if (!previousHasSpeech) {
restartIdleCheck() restartIdleCheck()
}
sendEvent( sendEvent(
"recognizing", mapOf( "recognizing", mapOf(
@ -637,19 +626,16 @@ object AgentService : CoroutineScope {
processWithChatApi(result) processWithChatApi(result)
} }
// 重置状态,继续识别
val previousHasSpeech = hasSpeechDetected
_hasSpeechDetected.set(false) _hasSpeechDetected.set(false)
if (previousHasSpeech) {
restartIdleCheck() restartIdleCheck()
}
} }
override fun onSessionStarted() { override fun onSessionStarted() {
sendEvent("recognition_started", mapOf("status" to "started")) sendEvent("recognition_started", mapOf("status" to "started"))
// 使用模式感知的空闲检测启动方法 // 使用模式感知的空闲检测启动方法
startIdleCheckForMode(currentRecognitionMode)
audioPlayer?.playAudio(R.raw.start) audioPlayer?.playAudio(R.raw.start)
} }
@ -677,14 +663,14 @@ object AgentService : CoroutineScope {
recognizeCallback() recognizeCallback()
} }
override fun onError(error: String) { override fun onError(code: Int, error: String) {
_isRecognitionActive.set(false) _isRecognitionActive.set(false)
stopIdleCheck() stopIdleCheck()
BleService.closeCodec() BleService.closeCodec()
Log.d(TAG, "语音识别错误: $error") Log.d(TAG, "语音识别错误: $error")
sendEvent( sendEvent(
"error", mapOf( "error", mapOf(
"code" to "RECOGNITION_ERROR", "code" to code,
"message" to error "message" to error
) )
) )

257
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -35,6 +35,12 @@ import java.util.concurrent.atomic.AtomicBoolean
* 针对回音消除的场景,使用拉流模式 (PullAudioInputStreamCallback) 实现,避免复杂的协程处理 * 针对回音消除的场景,使用拉流模式 (PullAudioInputStreamCallback) 实现,避免复杂的协程处理
* 通过检测扬声器和耳机状态,自动决定是否启用回音消除 * 通过检测扬声器和耳机状态,自动决定是否启用回音消除
*/ */
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
class AzureAsrHelper(private val context: Context) { class AzureAsrHelper(private val context: Context) {
private val tag = "AzureAsrHelper" private val tag = "AzureAsrHelper"
@ -60,7 +66,11 @@ class AzureAsrHelper(private val context: Context) {
// 音频处理 // 音频处理
var audioStream: SimpleAudioReceiver? = null var audioStream: SimpleAudioReceiver? = null
// 网络状态监听相关
private var connectivityManager: ConnectivityManager? = null
private var networkCallback: ConnectivityManager.NetworkCallback? = null
private var isNetworkAvailable = true
private var isNetworkRecovering = false
@ -142,6 +152,9 @@ class AzureAsrHelper(private val context: Context) {
} }
// 初始化网络监听
initNetworkMonitoring()
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "初始化失败: ${e.message}") Log.e(tag, "初始化失败: ${e.message}")
@ -233,7 +246,7 @@ class AzureAsrHelper(private val context: Context) {
audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE
) { ) {
if (speechConfig == null) { if (speechConfig == null) {
callback.onError("语音服务未初始化") callback.onError(1002,"语音服务未初始化")
return return
} }
@ -268,26 +281,59 @@ class AzureAsrHelper(private val context: Context) {
?: supportedLanguages[0] ?: supportedLanguages[0]
callback.onResult(result.text, detectedLanguage) callback.onResult(result.text, detectedLanguage)
} else { } else {
callback.onError("未能识别语音") callback.onError(1004,"未能识别语音")
} }
} catch (e: Exception) { } catch (e: Exception) {
stopAudioProcessing() stopAudioProcessing()
callback.onError("识别异常: ${e.message}") callback.onError(1003,"识别异常: ${e.message}")
}
}
/**
* 检查当前网络状态
* @return true表示网络可用,false表示网络不可用
*/
private fun checkNetworkStatus(): Boolean {
try {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork ?: return false
val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
} else {
@Suppress("DEPRECATION")
val networkInfo = connectivityManager.activeNetworkInfo
return networkInfo?.isConnectedOrConnecting == true
}
} catch (e: Exception) {
Log.e(tag, "检查网络状态失败: ${e.message}")
return false
} }
} }
/** /**
* 开始连续语音识别 * 开始连续语音识别
* @param audioSourceType 音频源类型 * @param audioSourceType 音频源类型
* @param audioDataCallback 音频数据回调接口 * @param audioDataCallback 音频数据回调
* @return 是否成功启动 * @return 是否成功开始识别
*/ */
fun startContinuousRecognition( fun startContinuousRecognition(
audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE, audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE,
audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = null audioDataCallback: SimpleAudioReceiver.AudioDataCallback? = null
): Boolean { ): Boolean {
Log.d(tag, "startContinuousRecognition:$isContinuousRecognitionActive ") Log.d(tag, "startContinuousRecognition:$isContinuousRecognitionActive ")
// 检查网络状态
if (!checkNetworkStatus()) {
Log.e(tag, "网络不可用,无法开始语音识别")
continuousCallback?.onError(1000,"网络连接不可用,请检查网络设置")
return false
}
if (isContinuousRecognitionActive) { if (isContinuousRecognitionActive) {
return true return true
} }
@ -299,15 +345,15 @@ class AzureAsrHelper(private val context: Context) {
} }
} }
this.audioSourceType = audioSourceType this.audioSourceType = audioSourceType
try { try {
Log.d(tag, "startContinuousRecognition: ${audioStream!!.isWriting()}")
Log.d(tag, "startContinuousRecognition: ")
// 启动音频处理(若已启动则跳过) // 启动音频处理(若已启动则跳过)
if (!audioStream!!.isWriting()) { if (!audioStream!!.isWriting()) {
Log.d(tag, "startAudioRecord:$audioSourceType")
audioStream!!.startAudioRecord( audioStream!!.startAudioRecord(
when (audioSourceType) { when (audioSourceType) {
AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE
AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL
}, },
@ -316,7 +362,7 @@ class AzureAsrHelper(private val context: Context) {
} else { } else {
Log.d(tag, "音频已启动,跳过重复启动") Log.d(tag, "音频已启动,跳过重复启动")
} }
recognizer?.startContinuousRecognitionAsync() recognizer?.startContinuousRecognitionAsync()
isContinuousRecognitionActive = true isContinuousRecognitionActive = true
return true return true
@ -334,7 +380,7 @@ class AzureAsrHelper(private val context: Context) {
fun setupEventListeners(callback: ContinuousRecognizeCallback): Boolean { fun setupEventListeners(callback: ContinuousRecognizeCallback): Boolean {
Log.d(tag, "设置ssssss监听器:${speechConfig} ") Log.d(tag, "设置ssssss监听器:${speechConfig} ")
if (speechConfig == null) { if (speechConfig == null) {
callback.onError("语音服务未初始化") callback.onError(1002,"语音服务未初始化")
return false return false
} }
@ -399,18 +445,31 @@ class AzureAsrHelper(private val context: Context) {
} }
) )
// 取消事件 // 取消事件
recognizer?.canceled?.addEventListener( recognizer?.canceled?.addEventListener(
EventHandler<SpeechRecognitionCanceledEventArgs> { _, event -> EventHandler<SpeechRecognitionCanceledEventArgs> { _, event ->
val errorDetails = event.errorDetails ?: "未知错误" val errorDetails = event.errorDetails ?: "未知错误"
val reason = event.reason.toString() val reason = event.reason.toString()
callback.onCanceled(reason, errorDetails)
Log.d(tag, "识别被取消: reason=$reason, details=$errorDetails")
// 检查是否是网络相关错误
if (isNetworkRelatedError(reason, errorDetails)) {
Log.d(tag, "检测到网络相关错误,停止音频处理")
stopAudioProcessingImmediately()
// 如果不是正在恢复网络,通知上层网络错误
if (!isNetworkRecovering) {
callback.onError(1000,"启动连续识别失败,网络连接已断开")
}
} else {
// 非网络错误,正常处理
callback.onCanceled(reason, errorDetails)
}
} }
) )
} catch (e: Exception) { } catch (e: Exception) {
callback.onError("启动连续识别失败: ${e.message}") callback.onError(1001,"启动连续识别失败: ${e.message}")
return false return false
} }
return true return true
@ -485,6 +544,14 @@ class AzureAsrHelper(private val context: Context) {
// recognizer?.stopContinuousRecognitionAsync()?.get(1000, TimeUnit.MILLISECONDS) // recognizer?.stopContinuousRecognitionAsync()?.get(1000, TimeUnit.MILLISECONDS)
// 停止音频处理 // 停止音频处理
stopAudioProcessing() stopAudioProcessing()
// 清理网络监听
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && networkCallback != null) {
connectivityManager?.unregisterNetworkCallback(networkCallback!!)
networkCallback = null
}
connectivityManager = null
// 停止录音 // 停止录音
if (audioStream!!.recordfile != null) { if (audioStream!!.recordfile != null) {
audioStream!!.recordfile!!.closeFile(true) audioStream!!.recordfile!!.closeFile(true)
@ -647,9 +714,10 @@ class AzureAsrHelper(private val context: Context) {
/** /**
* 识别错误时调用 * 识别错误时调用
* *
* @param code 错误码
* @param error 错误信息 * @param error 错误信息
*/ */
fun onError(error: String) fun onError(code: Int,error: String)
} }
/** /**
@ -696,10 +764,153 @@ class AzureAsrHelper(private val context: Context) {
* *
* @param error 错误信息 * @param error 错误信息
*/ */
fun onError(error: String) fun onError(code: Int,error: String)
} }
/**
* 检查是否是网络相关错误
*/
private fun isNetworkRelatedError(reason: String, errorDetails: String): Boolean {
val networkErrorKeywords = arrayOf(
"network", "connection", "timeout", "unreachable",
"dns", "socket", "ssl", "tls", "certificate",
"网络", "连接", "超时", "不可达"
)
val combinedText = "$reason $errorDetails".lowercase()
return networkErrorKeywords.any { keyword ->
combinedText.contains(keyword.lowercase())
}
}
/**
* 初始化网络状态监听
*/
private fun initNetworkMonitoring() {
try {
connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.d(tag, "网络连接可用")
handleNetworkAvailable()
}
override fun onLost(network: Network) {
super.onLost(network)
Log.d(tag, "网络连接丢失")
handleNetworkLost()
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities)
val hasInternet = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
Log.d(tag, "网络能力变化,是否有网络: $hasInternet")
if (hasInternet && !isNetworkAvailable) {
handleNetworkAvailable()
} else if (!hasInternet && isNetworkAvailable) {
handleNetworkLost()
}
}
}
val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager?.registerNetworkCallback(networkRequest, networkCallback!!)
}
} catch (e: Exception) {
Log.e(tag, "初始化网络监听失败: ${e.message}")
}
}
/**
* 处理网络连接可用
*/
private fun handleNetworkAvailable() {
if (!isNetworkAvailable) {
isNetworkAvailable = true
isNetworkRecovering = true
Log.d(tag, "网络恢复,准备重新启动识别")
// 如果之前在进行连续识别,重新启动
Handler(Looper.getMainLooper()).postDelayed({
restartContinuousRecognition()
}, 1000) // 延迟1秒确保网络稳定
}
}
/**
* 处理网络连接丢失
*/
private fun handleNetworkLost() {
if (isNetworkAvailable) {
isNetworkAvailable = false
Log.d(tag, "网络断开,停止音频处理")
stopAudioProcessingImmediately()
}
}
/**
* 立即停止音频处理(网络断开时使用)
*/
private fun stopAudioProcessingImmediately() {
try {
Log.d(tag, "立即停止音频处理")
stopContinuousRecognition()
// 不改变isContinuousRecognitionActive状态,保持识别意图
} catch (e: Exception) {
Log.e(tag, "立即停止音频处理失败: ${e.message}")
}
}
/**
* 重新启动连续识别(网络恢复时使用)
*/
private fun restartContinuousRecognition() {
try {
Log.d(tag, "重新启动连续识别")
// 检查continuousCallback是否为空
val callback = continuousCallback
if (callback == null) {
Log.e(tag, "无法重新启动连续识别:回调为空")
return
}
setupEventListeners(callback)
// 重新启动音频处理
if (audioStream?.isWriting() != true) {
audioStream?.startAudioRecord(
when (audioSourceType) {
AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE
AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL
},
null
)
}
// 重新启动连续识别
recognizer?.startContinuousRecognitionAsync()
isNetworkRecovering = false
Log.d(tag, "连续识别重新启动成功")
// 通知上层网络已恢复
//continuousCallback?.onError("网络已恢复")
} catch (e: Exception) {
Log.e(tag, "重新启动连续识别失败: ${e.message}")
isNetworkRecovering = false
}
}
} }
Loading…
Cancel
Save