|
|
@ -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,19 +281,44 @@ 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, |
|
|
@ -288,6 +326,14 @@ class AzureAsrHelper(private val context: Context) { |
|
|
): 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 |
|
|
} |
|
|
} |
|
|
@ -301,13 +347,13 @@ 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 |
|
|
}, |
|
|
}, |
|
|
@ -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 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -404,13 +450,26 @@ class AzureAsrHelper(private val context: Context) { |
|
|
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 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|