10 changed files with 1417 additions and 1563 deletions
@ -0,0 +1,194 @@ |
|||||
|
package com.example.deep_voice |
||||
|
|
||||
|
import android.util.Log |
||||
|
import com.microsoft.cognitiveservices.speech.* |
||||
|
import com.microsoft.cognitiveservices.speech.util.EventHandler |
||||
|
import java.util.concurrent.ExecutionException |
||||
|
import java.util.function.Consumer |
||||
|
|
||||
|
class SpeechRecognitionHelper { |
||||
|
private var recognizer: SpeechRecognizer? = null |
||||
|
private val TAG = "SpeechRecognitionHelper" |
||||
|
private var isContinuousRecognitionActive = false |
||||
|
|
||||
|
// 初始化 SDK |
||||
|
fun initialize(subscriptionKey: String, serviceRegion: String) { |
||||
|
try { |
||||
|
val config = SpeechConfig.fromSubscription(subscriptionKey, serviceRegion) |
||||
|
// 设置识别语言,例如中文 |
||||
|
config.speechRecognitionLanguage = "zh-CN" |
||||
|
recognizer = SpeechRecognizer(config) |
||||
|
Log.d(TAG, "Speech SDK initialized successfully") |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "初始化失败: ${e.message}") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 开始一次性语音识别 |
||||
|
fun recognizeOnce(callback: RecognizeCallback) { |
||||
|
if (recognizer == null) { |
||||
|
callback.onError("SpeechRecognizer 未初始化") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 使用同步方式调用,避免 CompletableFuture 的兼容性问题 |
||||
|
val result = recognizer?.recognizeOnceAsync()?.get() |
||||
|
|
||||
|
if (result != null) { |
||||
|
when (result.reason) { |
||||
|
ResultReason.RecognizedSpeech -> { |
||||
|
callback.onResult(result.text) |
||||
|
} |
||||
|
else -> { |
||||
|
callback.onError("识别失败,原因: ${result.reason}") |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
callback.onError("识别结果为空") |
||||
|
} |
||||
|
} catch (e: Exception) { |
||||
|
when (e) { |
||||
|
is InterruptedException, is ExecutionException -> { |
||||
|
Log.e(TAG, "识别异常: ${e.message}") |
||||
|
callback.onError("识别异常: ${e.message}") |
||||
|
} |
||||
|
else -> { |
||||
|
Log.e(TAG, "未知异常: ${e.message}") |
||||
|
callback.onError("未知异常: ${e.message}") |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 开始连续语音识别 |
||||
|
fun startContinuousRecognition(callback: ContinuousRecognizeCallback) { |
||||
|
if (recognizer == null) { |
||||
|
callback.onError("SpeechRecognizer 未初始化") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (isContinuousRecognitionActive) { |
||||
|
callback.onError("连续识别已经在进行中") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 设置识别事件处理 |
||||
|
recognizer?.let { recognizer -> |
||||
|
// 设置识别事件处理 |
||||
|
recognizer.recognized.addEventListener( |
||||
|
EventHandler<SpeechRecognitionEventArgs> { _, event -> |
||||
|
if (event.result.reason == ResultReason.RecognizedSpeech) { |
||||
|
callback.onResult(event.result.text) |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
// 设置识别中事件处理(实时反馈) |
||||
|
recognizer.recognizing.addEventListener( |
||||
|
EventHandler<SpeechRecognitionEventArgs> { _, event -> |
||||
|
if (event.result.reason == ResultReason.RecognizingSpeech) { |
||||
|
callback.onRecognizing(event.result.text) |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
// 设置会话开始事件处理 |
||||
|
recognizer.sessionStarted.addEventListener( |
||||
|
EventHandler<SessionEventArgs> { _, _ -> |
||||
|
callback.onSessionStarted() |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
// 设置会话结束事件处理 |
||||
|
recognizer.sessionStopped.addEventListener( |
||||
|
EventHandler<SessionEventArgs> { _, _ -> |
||||
|
isContinuousRecognitionActive = false |
||||
|
callback.onSessionStopped() |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
// 设置取消事件处理 |
||||
|
recognizer.canceled.addEventListener( |
||||
|
EventHandler<SpeechRecognitionCanceledEventArgs> { _, event -> |
||||
|
val reason = event.reason |
||||
|
val errorDetails = if (reason == CancellationReason.Error) event.errorDetails else "" |
||||
|
callback.onCanceled(reason.toString(), errorDetails) |
||||
|
isContinuousRecognitionActive = false |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
// 开始连续识别 |
||||
|
recognizer.startContinuousRecognitionAsync().get() |
||||
|
isContinuousRecognitionActive = true |
||||
|
Log.d(TAG, "连续识别已开始") |
||||
|
} |
||||
|
|
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "开始连续识别失败: ${e.message}") |
||||
|
callback.onError("开始连续识别失败: ${e.message}") |
||||
|
isContinuousRecognitionActive = false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 停止连续语音识别 |
||||
|
fun stopContinuousRecognition(callback: ContinuousRecognizeCallback) { |
||||
|
if (recognizer == null) { |
||||
|
callback.onError("SpeechRecognizer 未初始化") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (!isContinuousRecognitionActive) { |
||||
|
callback.onError("连续识别未在进行中") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 停止连续识别 |
||||
|
recognizer?.stopContinuousRecognitionAsync()?.get() |
||||
|
isContinuousRecognitionActive = false |
||||
|
Log.d(TAG, "连续识别已停止") |
||||
|
callback.onSessionStopped() |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "停止连续识别失败: ${e.message}") |
||||
|
callback.onError("停止连续识别失败: ${e.message}") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 检查连续识别是否活跃 |
||||
|
fun isContinuousRecognitionActive(): Boolean { |
||||
|
return isContinuousRecognitionActive |
||||
|
} |
||||
|
|
||||
|
// 释放资源 |
||||
|
fun dispose() { |
||||
|
try { |
||||
|
if (isContinuousRecognitionActive) { |
||||
|
recognizer?.stopContinuousRecognitionAsync()?.get() |
||||
|
isContinuousRecognitionActive = false |
||||
|
} |
||||
|
recognizer?.close() |
||||
|
recognizer = null |
||||
|
Log.d(TAG, "语音识别资源已释放") |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "释放资源失败: ${e.message}") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 一次性识别回调接口 |
||||
|
interface RecognizeCallback { |
||||
|
fun onResult(result: String) |
||||
|
fun onError(error: String) |
||||
|
} |
||||
|
|
||||
|
// 连续识别回调接口 |
||||
|
interface ContinuousRecognizeCallback { |
||||
|
fun onResult(result: String) |
||||
|
fun onRecognizing(recognizing: String) |
||||
|
fun onSessionStarted() |
||||
|
fun onSessionStopped() |
||||
|
fun onCanceled(reason: String, errorDetails: String) |
||||
|
fun onError(error: String) |
||||
|
} |
||||
|
} |
||||
@ -1,493 +0,0 @@ |
|||||
package com.example.deep_voice |
|
||||
|
|
||||
import android.app.Activity |
|
||||
import android.content.Context |
|
||||
import android.util.Log |
|
||||
import com.bytedance.speech.speechengine.SpeechEngine |
|
||||
import com.bytedance.speech.speechengine.SpeechEngineDefines |
|
||||
import com.bytedance.speech.speechengine.SpeechEngineGenerator |
|
||||
import io.flutter.plugin.common.BinaryMessenger |
|
||||
import io.flutter.plugin.common.EventChannel |
|
||||
import io.flutter.plugin.common.MethodChannel |
|
||||
import io.flutter.plugin.common.MethodChannel.Result |
|
||||
import org.json.JSONObject |
|
||||
import java.io.File |
|
||||
|
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* 火山语音识别桥接类 |
|
||||
* 负责与火山语音识别 SDK 交互,并将结果通过 MethodChannel 和 EventChannel 传递给 Flutter 层 |
|
||||
*/ |
|
||||
class VolcSpeechBridge( |
|
||||
private val activity: Activity, |
|
||||
messenger: BinaryMessenger |
|
||||
) : SpeechEngine.SpeechListener { |
|
||||
|
|
||||
companion object { |
|
||||
private const val TAG = "VolcSpeechBridge" |
|
||||
private const val CHANNEL_NAME = "com.example.deep_voice/speech_recognition" |
|
||||
private const val EVENT_CHANNEL_NAME = "com.example.deep_voice/speech_recognition_events" |
|
||||
} |
|
||||
|
|
||||
private var engine: SpeechEngine? = null |
|
||||
private var engineHandler: Long = 0 |
|
||||
private var methodResult: Result? = null |
|
||||
private var eventSink: EventChannel.EventSink? = null |
|
||||
private var isInitialized = false |
|
||||
|
|
||||
// 初始化 MethodChannel 和 EventChannel |
|
||||
init { |
|
||||
// 设置 MethodChannel 处理方法调用 |
|
||||
MethodChannel(messenger, CHANNEL_NAME).setMethodCallHandler { call, result -> |
|
||||
when (call.method) { |
|
||||
"initialize" -> { |
|
||||
val appKey = call.argument<String>("appKey") ?: "" |
|
||||
val accessKey = call.argument<String>("accessKey") ?: "" |
|
||||
val cluster = call.argument<String>("cluster") ?: "volcano_asr" |
|
||||
initialize(appKey, accessKey, cluster, result) |
|
||||
} |
|
||||
"startRecognition" -> { |
|
||||
startRecognition(result) |
|
||||
} |
|
||||
"stopRecognition" -> { |
|
||||
stopRecognition(result) |
|
||||
} |
|
||||
"isInitialized" -> { |
|
||||
result.success(isInitialized) |
|
||||
} |
|
||||
"checkEngineCapabilities" -> { |
|
||||
checkEngineCapabilities(result) |
|
||||
} |
|
||||
else -> { |
|
||||
result.notImplemented() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 设置 EventChannel 处理事件流 |
|
||||
EventChannel(messenger, EVENT_CHANNEL_NAME).setStreamHandler(object : EventChannel.StreamHandler { |
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { |
|
||||
eventSink = events |
|
||||
} |
|
||||
|
|
||||
override fun onCancel(arguments: Any?) { |
|
||||
eventSink = null |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 初始化火山语音识别引擎 |
|
||||
*/ |
|
||||
fun initialize(appKey: String, accessKey: String, cluster: String, result: Result) { |
|
||||
methodResult = result |
|
||||
try { |
|
||||
Log.i(TAG, "Initializing Volcengine Speech Engine") |
|
||||
|
|
||||
// 前置操作:环境依赖 |
|
||||
SpeechEngineGenerator.PrepareEnvironment(activity.applicationContext, activity.application) |
|
||||
|
|
||||
// 创建引擎实例 |
|
||||
engine = SpeechEngineGenerator.getInstance() |
|
||||
engineHandler = engine?.createEngine() ?: 0 |
|
||||
|
|
||||
if (engineHandler == 0L) { |
|
||||
Log.e(TAG, "Failed to create engine instance") |
|
||||
result.error("CREATE_ERROR", "Failed to create engine instance", null) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// 设置引擎类型 |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ENGINE_NAME_STRING, SpeechEngineDefines.ASR_ENGINE) |
|
||||
|
|
||||
// 设置用户ID(必填) |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_UID_STRING, "deep_voice_user") |
|
||||
|
|
||||
// 设置设备ID(选填) |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_DEVICE_ID_STRING, "deep_voice_device") |
|
||||
|
|
||||
// 设置日志级别 |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_LOG_LEVEL_STRING, SpeechEngineDefines.LOG_LEVEL_WARN) |
|
||||
|
|
||||
// 禁用日志文件,避免权限问题 |
|
||||
engine?.setOptionBoolean(engineHandler, "enable_log_file", false) |
|
||||
|
|
||||
// 设置应用 ID 和访问密钥 |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_APP_ID_STRING, appKey) |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_APP_TOKEN_STRING, "Bearer;$accessKey") |
|
||||
|
|
||||
// 设置网络参数 |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_ADDRESS_STRING, "wss://openspeech.bytedance.com") |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_URI_STRING, "/api/v2/asr") |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_CLUSTER_STRING, cluster) |
|
||||
|
|
||||
// 设置音频来源为内置录音机 |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_RECORDER_TYPE_STRING, SpeechEngineDefines.RECORDER_TYPE_RECORDER) |
|
||||
|
|
||||
// 启用音量获取 |
|
||||
engine?.setOptionBoolean(engineHandler, SpeechEngineDefines.PARAMS_KEY_ENABLE_GET_VOLUME_BOOL, true) |
|
||||
|
|
||||
// 设置最大录音时长(60秒) |
|
||||
engine?.setOptionInt(engineHandler, SpeechEngineDefines.PARAMS_KEY_VAD_MAX_SPEECH_DURATION_INT, 60000) |
|
||||
|
|
||||
// 控制识别效果 |
|
||||
engine?.setOptionBoolean(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_SHOW_NLU_PUNC_BOOL, true) // 返回标点符号 |
|
||||
engine?.setOptionBoolean(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_AUTO_STOP_BOOL, true) // 开启自动判停 |
|
||||
|
|
||||
// 设置结果返回形式为增量返回 |
|
||||
engine?.setOptionString(engineHandler, SpeechEngineDefines.PARAMS_KEY_ASR_RESULT_TYPE_STRING, SpeechEngineDefines.ASR_RESULT_TYPE_SINGLE) |
|
||||
|
|
||||
// 设置上下文 |
|
||||
engine?.setContext(activity.applicationContext) |
|
||||
|
|
||||
// 设置监听器 |
|
||||
engine?.setListener(this) |
|
||||
|
|
||||
// 初始化引擎 |
|
||||
Log.i(TAG, "Calling initEngine()") |
|
||||
val ret = engine?.initEngine(engineHandler) |
|
||||
Log.i(TAG, "initEngine() returned: $ret") |
|
||||
|
|
||||
if (ret == SpeechEngineDefines.ERR_NO_ERROR) { |
|
||||
isInitialized = true |
|
||||
Log.i(TAG, "Volcengine Speech Engine initialized successfully") |
|
||||
result.success("Engine initialized") |
|
||||
|
|
||||
// 发送状态更新到 Flutter |
|
||||
sendEvent("status", "initialized") |
|
||||
} else { |
|
||||
Log.e(TAG, "Engine initialization failed with error code: $ret") |
|
||||
result.error("INIT_ERROR", "Engine initialization failed with error code: $ret", null) |
|
||||
} |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Initialization failed", e) |
|
||||
result.error("INIT_ERROR", e.message, null) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 启动语音识别 |
|
||||
*/ |
|
||||
fun startRecognition(result: Result) { |
|
||||
try { |
|
||||
if (!isInitialized) { |
|
||||
Log.e(TAG, "Cannot start recognition: Engine not initialized") |
|
||||
result.error("NOT_INITIALIZED", "Speech engine is not initialized", null) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
Log.i(TAG, "Starting speech recognition") |
|
||||
|
|
||||
// 先调用同步停止,避免SDK内部异步线程带来的问题 |
|
||||
engine?.sendDirective(engineHandler, SpeechEngineDefines.DIRECTIVE_SYNC_STOP_ENGINE, "") |
|
||||
|
|
||||
// 启动引擎 |
|
||||
val ret = engine?.sendDirective(engineHandler, SpeechEngineDefines.DIRECTIVE_START_ENGINE, "") |
|
||||
Log.d(TAG, "Start engine directive returned: $ret") |
|
||||
|
|
||||
if (ret == SpeechEngineDefines.ERR_NO_ERROR) { |
|
||||
Log.i(TAG, "Successfully started recognition") |
|
||||
result.success("Recognition started") |
|
||||
// 状态会通过回调更新 |
|
||||
} else { |
|
||||
Log.e(TAG, "Start recognition failed with error code: $ret") |
|
||||
result.error("START_ERROR", "Start recognition failed with error code: $ret", null) |
|
||||
} |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Start recognition failed", e) |
|
||||
result.error("START_ERROR", e.message, null) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 停止语音识别 |
|
||||
*/ |
|
||||
fun stopRecognition(result: Result) { |
|
||||
try { |
|
||||
if (!isInitialized) { |
|
||||
Log.e(TAG, "Cannot stop recognition: Engine not initialized") |
|
||||
result.error("NOT_INITIALIZED", "Speech engine is not initialized", null) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
Log.i(TAG, "Stopping speech recognition") |
|
||||
|
|
||||
// 先发送音频输入完成指令 |
|
||||
val finishRet = engine?.sendDirective(engineHandler, SpeechEngineDefines.DIRECTIVE_FINISH_TALKING, "") |
|
||||
Log.d(TAG, "Finish talking directive returned: $finishRet") |
|
||||
|
|
||||
// 然后停止引擎 |
|
||||
val stopRet = engine?.sendDirective(engineHandler, SpeechEngineDefines.DIRECTIVE_STOP_ENGINE, "") |
|
||||
Log.d(TAG, "Stop engine directive returned: $stopRet") |
|
||||
|
|
||||
if (finishRet == SpeechEngineDefines.ERR_NO_ERROR || stopRet == SpeechEngineDefines.ERR_NO_ERROR) { |
|
||||
Log.i(TAG, "Successfully stopped recognition") |
|
||||
result.success("Recognition stopped") |
|
||||
// 状态会通过回调更新 |
|
||||
} else { |
|
||||
Log.e(TAG, "Stop recognition failed with error codes: finish=$finishRet, stop=$stopRet") |
|
||||
result.error("STOP_ERROR", "Stop recognition failed", null) |
|
||||
} |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Stop recognition failed", e) |
|
||||
result.error("STOP_ERROR", e.message, null) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 释放资源 |
|
||||
*/ |
|
||||
fun dispose() { |
|
||||
try { |
|
||||
if (engineHandler != 0L) { |
|
||||
engine?.destroyEngine(engineHandler) |
|
||||
engineHandler = 0 |
|
||||
} |
|
||||
engine = null |
|
||||
isInitialized = false |
|
||||
Log.i(TAG, "Volcengine Speech Engine released") |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Error releasing engine", e) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// ------------------ |
|
||||
// 发送事件到 Flutter |
|
||||
// ------------------ |
|
||||
|
|
||||
private fun sendEvent(eventName: String, data: Any) { |
|
||||
try { |
|
||||
val eventData = JSONObject().apply { |
|
||||
put("event", eventName) |
|
||||
put("data", data) |
|
||||
} |
|
||||
|
|
||||
eventSink?.success(eventData.toString()) |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Error sending event", e) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// ------------------ |
|
||||
// SpeechListener 接口回调 |
|
||||
// ------------------ |
|
||||
|
|
||||
/** |
|
||||
* 处理语音消息回调 |
|
||||
* 这是 SpeechListener 接口的必须实现方法 |
|
||||
*/ |
|
||||
override fun onSpeechMessage(messageType: Int, messageData: ByteArray?, messageLength: Int) { |
|
||||
try { |
|
||||
Log.d(TAG, "Received speech message type: $messageType, length: $messageLength") |
|
||||
|
|
||||
if (messageData != null) { |
|
||||
val dataString = try { |
|
||||
String(messageData, 0, messageLength) |
|
||||
} catch (e: Exception) { |
|
||||
"Unable to convert to string: ${e.message}" |
|
||||
} |
|
||||
Log.d(TAG, "Message data: $dataString") |
|
||||
} |
|
||||
|
|
||||
when (messageType) { |
|
||||
// 引擎启动成功 |
|
||||
SpeechEngineDefines.MESSAGE_TYPE_ENGINE_START -> { |
|
||||
Log.i(TAG, "Engine started successfully") |
|
||||
sendEvent("status", "listening") |
|
||||
} |
|
||||
|
|
||||
// 引擎关闭 |
|
||||
SpeechEngineDefines.MESSAGE_TYPE_ENGINE_STOP -> { |
|
||||
Log.i(TAG, "Engine stopped") |
|
||||
sendEvent("status", "idle") |
|
||||
} |
|
||||
|
|
||||
// 错误信息 |
|
||||
SpeechEngineDefines.MESSAGE_TYPE_ENGINE_ERROR -> { |
|
||||
messageData?.let { |
|
||||
val errorJson = String(it, 0, messageLength) |
|
||||
Log.e(TAG, "Engine error: $errorJson") |
|
||||
|
|
||||
try { |
|
||||
val jsonObject = JSONObject(errorJson) |
|
||||
val errorCode = jsonObject.optInt("err_code", -1) |
|
||||
val errorMsg = jsonObject.optString("err_msg", "Unknown error") |
|
||||
val reqId = jsonObject.optString("req_id", "") |
|
||||
|
|
||||
Log.e(TAG, "Error details: code=$errorCode, message=$errorMsg, reqId=$reqId") |
|
||||
|
|
||||
// 通知 Flutter 识别出错 |
|
||||
val errorData = JSONObject() |
|
||||
errorData.put("code", errorCode) |
|
||||
errorData.put("message", errorMsg) |
|
||||
errorData.put("reqId", reqId) |
|
||||
|
|
||||
sendEvent("error", errorData.toString()) |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Error parsing error JSON", e) |
|
||||
sendEvent("error", "Error code: Unknown") |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// VAD 状态变化消息 |
|
||||
SpeechEngineDefines.MESSAGE_TYPE_VAD_STATE -> { |
|
||||
val state = messageData?.let { String(it, 0, messageLength).toIntOrNull() } ?: -1 |
|
||||
Log.i(TAG, "VAD state changed: $state") |
|
||||
|
|
||||
val vadState = when (state) { |
|
||||
0 -> "silence" |
|
||||
1 -> "speech" |
|
||||
else -> "unknown" |
|
||||
} |
|
||||
|
|
||||
sendEvent("vad", vadState) |
|
||||
} |
|
||||
|
|
||||
// 中间识别结果 |
|
||||
SpeechEngineDefines.MESSAGE_TYPE_PARTIAL_RESULT -> { |
|
||||
messageData?.let { |
|
||||
val resultJson = String(it, 0, messageLength) |
|
||||
Log.d(TAG, "Partial recognition result: $resultJson") |
|
||||
|
|
||||
try { |
|
||||
val jsonObject = JSONObject(resultJson) |
|
||||
|
|
||||
if (jsonObject.has("result")) { |
|
||||
val resultArray = jsonObject.getJSONArray("result") |
|
||||
if (resultArray.length() > 0) { |
|
||||
val result = resultArray.getJSONObject(0) |
|
||||
val text = result.optString("text", "") |
|
||||
|
|
||||
if (text.isNotEmpty()) { |
|
||||
Log.i(TAG, "Partial recognition text: $text") |
|
||||
|
|
||||
// 发送识别结果到 Flutter |
|
||||
val resultData = JSONObject() |
|
||||
resultData.put("text", text) |
|
||||
resultData.put("isFinal", false) |
|
||||
|
|
||||
sendEvent("result", resultData.toString()) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Error parsing partial result JSON", e) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 最终识别结果 |
|
||||
SpeechEngineDefines.MESSAGE_TYPE_FINAL_RESULT -> { |
|
||||
messageData?.let { |
|
||||
val resultJson = String(it, 0, messageLength) |
|
||||
Log.d(TAG, "Final recognition result: $resultJson") |
|
||||
|
|
||||
try { |
|
||||
val jsonObject = JSONObject(resultJson) |
|
||||
|
|
||||
if (jsonObject.has("result")) { |
|
||||
val resultArray = jsonObject.getJSONArray("result") |
|
||||
if (resultArray.length() > 0) { |
|
||||
val result = resultArray.getJSONObject(0) |
|
||||
val text = result.optString("text", "") |
|
||||
|
|
||||
if (text.isNotEmpty()) { |
|
||||
Log.i(TAG, "Final recognition text: $text") |
|
||||
|
|
||||
// 发送识别结果到 Flutter |
|
||||
val resultData = JSONObject() |
|
||||
resultData.put("text", text) |
|
||||
resultData.put("isFinal", true) |
|
||||
|
|
||||
sendEvent("result", resultData.toString()) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Error parsing final result JSON", e) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 当前音量 |
|
||||
SpeechEngineDefines.MESSAGE_TYPE_VOLUME -> { |
|
||||
val volume = messageData?.let { String(it, 0, messageLength).toFloatOrNull() } ?: 0f |
|
||||
// 将 [0-1] 的音量值转换为 [0-100] 的整数 |
|
||||
val volumeInt = (volume * 100).toInt().coerceIn(0, 100) |
|
||||
Log.d(TAG, "Volume level: $volume (normalized: $volumeInt)") |
|
||||
|
|
||||
// 发送音量变化事件到 Flutter |
|
||||
sendEvent("volume", volumeInt) |
|
||||
} |
|
||||
|
|
||||
else -> { |
|
||||
Log.d(TAG, "Received unknown message type: $messageType") |
|
||||
// 尝试解析未知消息类型的数据 |
|
||||
messageData?.let { |
|
||||
try { |
|
||||
val dataString = String(it, 0, messageLength) |
|
||||
Log.d(TAG, "Unknown message data: $dataString") |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Error parsing unknown message data", e) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Error processing speech message", e) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 检查引擎能力和支持的指令 |
|
||||
*/ |
|
||||
fun checkEngineCapabilities(result: Result) { |
|
||||
try { |
|
||||
Log.i(TAG, "Checking engine capabilities") |
|
||||
|
|
||||
if (engine == null) { |
|
||||
Log.e(TAG, "Engine is null, cannot check capabilities") |
|
||||
result.error("ENGINE_NULL", "Engine is null", null) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// 获取引擎版本信息 |
|
||||
val version = engine?.getVersion() ?: "Unknown" |
|
||||
Log.i(TAG, "Engine version: $version") |
|
||||
|
|
||||
// 创建能力信息对象 |
|
||||
val capabilities = JSONObject() |
|
||||
capabilities.put("version", version) |
|
||||
|
|
||||
// 列出支持的指令 |
|
||||
val supportedDirectives = JSONObject() |
|
||||
supportedDirectives.put("DIRECTIVE_START_ENGINE", SpeechEngineDefines.DIRECTIVE_START_ENGINE) |
|
||||
supportedDirectives.put("DIRECTIVE_STOP_ENGINE", SpeechEngineDefines.DIRECTIVE_STOP_ENGINE) |
|
||||
supportedDirectives.put("DIRECTIVE_FINISH_TALKING", SpeechEngineDefines.DIRECTIVE_FINISH_TALKING) |
|
||||
supportedDirectives.put("DIRECTIVE_SYNC_STOP_ENGINE", SpeechEngineDefines.DIRECTIVE_SYNC_STOP_ENGINE) |
|
||||
supportedDirectives.put("DIRECTIVE_UPDATE_ASR_HOTWORDS", SpeechEngineDefines.DIRECTIVE_UPDATE_ASR_HOTWORDS) |
|
||||
|
|
||||
capabilities.put("supported_directives", supportedDirectives) |
|
||||
|
|
||||
// 列出支持的消息类型 |
|
||||
val supportedMessageTypes = JSONObject() |
|
||||
supportedMessageTypes.put("MESSAGE_TYPE_ENGINE_START", SpeechEngineDefines.MESSAGE_TYPE_ENGINE_START) |
|
||||
supportedMessageTypes.put("MESSAGE_TYPE_ENGINE_STOP", SpeechEngineDefines.MESSAGE_TYPE_ENGINE_STOP) |
|
||||
supportedMessageTypes.put("MESSAGE_TYPE_ENGINE_ERROR", SpeechEngineDefines.MESSAGE_TYPE_ENGINE_ERROR) |
|
||||
supportedMessageTypes.put("MESSAGE_TYPE_PARTIAL_RESULT", SpeechEngineDefines.MESSAGE_TYPE_PARTIAL_RESULT) |
|
||||
supportedMessageTypes.put("MESSAGE_TYPE_FINAL_RESULT", SpeechEngineDefines.MESSAGE_TYPE_FINAL_RESULT) |
|
||||
supportedMessageTypes.put("MESSAGE_TYPE_VOLUME", SpeechEngineDefines.MESSAGE_TYPE_VOLUME) |
|
||||
supportedMessageTypes.put("MESSAGE_TYPE_VAD_STATE_CHANGED", SpeechEngineDefines.MESSAGE_TYPE_VAD_STATE) |
|
||||
|
|
||||
capabilities.put("supported_message_types", supportedMessageTypes) |
|
||||
|
|
||||
// 返回结果 |
|
||||
result.success(capabilities.toString()) |
|
||||
} catch (e: Exception) { |
|
||||
Log.e(TAG, "Error checking engine capabilities", e) |
|
||||
result.error("CHECK_ERROR", e.message, null) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,391 +1,281 @@ |
|||||
import 'dart:async'; |
import 'dart:async'; |
||||
import 'dart:convert'; |
|
||||
import 'package:flutter/services.dart'; |
import 'package:flutter/services.dart'; |
||||
import 'package:get/get.dart'; |
|
||||
import 'package:flutter/foundation.dart'; |
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart'; |
import 'package:flutter_dotenv/flutter_dotenv.dart'; |
||||
import 'dart:developer' as developer; |
|
||||
|
|
||||
/// 火山语音识别服务 |
/// 微软语音识别服务 |
||||
/// 负责与原生层的火山语音识别引擎交互 |
/// |
||||
class VoiceRecognitionService extends GetxService { |
/// 该服务提供了通过平台通道与 Android 上的 Microsoft Speech SDK 交互的接口 |
||||
// 方法通道,用于调用原生方法 |
class VoiceRecognitionService { |
||||
static const MethodChannel _methodChannel = |
static const MethodChannel _channel = MethodChannel('com.example.deep_voice/speech_recognition'); |
||||
MethodChannel('com.example.deep_voice/speech_recognition'); |
static const EventChannel _eventChannel = EventChannel('com.example.deep_voice/speech_recognition_events'); |
||||
|
|
||||
// 事件通道,用于接收原生层的事件 |
bool _isInitialized = false; |
||||
static const EventChannel _eventChannel = |
late final String _subscriptionKey; |
||||
EventChannel('com.example.deep_voice/speech_recognition_events'); |
late final String _serviceRegion; |
||||
|
|
||||
// 状态变量 |
|
||||
final isInitialized = false.obs; |
|
||||
final isListening = false.obs; |
|
||||
final isConnecting = true.obs; |
|
||||
|
|
||||
// 识别结果 |
// 连续识别相关 |
||||
final recognizedText = ''.obs; |
bool _isContinuousRecognitionActive = false; |
||||
|
StreamController<RecognitionEvent>? _eventStreamController; |
||||
|
StreamSubscription? _eventSubscription; |
||||
|
|
||||
// 回调函数 |
// 公开的事件流 |
||||
Function(String)? onRecognitionResult; |
Stream<RecognitionEvent>? _recognitionStream; |
||||
Function(String)? onError; |
Stream<RecognitionEvent>? get recognitionStream => _recognitionStream; |
||||
Function()? onRecognitionComplete; |
|
||||
Function(String)? onConnectionStatusChanged; |
|
||||
Function(String)? onSentenceComplete; |
|
||||
|
|
||||
// 事件流订阅 |
// 最新的识别结果 |
||||
StreamSubscription? _eventSubscription; |
String _latestRecognizedText = ''; |
||||
|
String get latestRecognizedText => _latestRecognizedText; |
||||
|
|
||||
// 火山语音识别的 AppKey 和 AccessKey |
VoiceRecognitionService() { |
||||
late final String _appKey; |
_loadConfig(); |
||||
late final String _accessKey; |
|
||||
late final String _cluster; |
|
||||
|
|
||||
@override |
|
||||
void onInit() { |
|
||||
developer.log('VoiceRecognitionService: 初始化服务', name: 'VoiceRecognition'); |
|
||||
super.onInit(); |
|
||||
_loadCredentials(); |
|
||||
_setupEventListener(); |
|
||||
} |
} |
||||
|
|
||||
/// 加载火山语音识别的凭证 |
/// 从环境变量加载配置 |
||||
void _loadCredentials() { |
void _loadConfig() { |
||||
try { |
_subscriptionKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; |
||||
_appKey = dotenv.env['VOLC_SPEECH_APP_KEY'] ?? ''; |
_serviceRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? ''; |
||||
_accessKey = dotenv.env['VOLC_SPEECH_ACCESS_KEY'] ?? ''; |
|
||||
_cluster = dotenv.env['VOLC_SPEECH_CLUSTER'] ?? 'volcano_asr'; |
if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { |
||||
|
throw Exception('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); |
||||
developer.log('VoiceRecognitionService: 加载凭证 - AppKey: ${_appKey.isNotEmpty ? '已设置' : '未设置'}, AccessKey: ${_accessKey.isNotEmpty ? '已设置' : '未设置'}, Cluster: $_cluster', name: 'VoiceRecognition'); |
|
||||
|
|
||||
if (_appKey.isEmpty || _accessKey.isEmpty) { |
|
||||
developer.log('VoiceRecognitionService: 警告 - 火山语音识别凭证未设置,请在 .env 文件中设置 VOLC_SPEECH_APP_KEY 和 VOLC_SPEECH_ACCESS_KEY', name: 'VoiceRecognition'); |
|
||||
} |
|
||||
} catch (e, stack) { |
|
||||
developer.log('VoiceRecognitionService: 加载火山语音识别凭证失败: $e', name: 'VoiceRecognition'); |
|
||||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
/// 设置事件监听器 |
/// 初始化微软语音识别 SDK |
||||
void _setupEventListener() { |
/// |
||||
|
/// 返回 true 表示初始化成功,否则抛出 PlatformException |
||||
|
Future<bool> initialize() async { |
||||
|
if (_isInitialized) return true; |
||||
|
|
||||
try { |
try { |
||||
developer.log('VoiceRecognitionService: 设置事件监听器', name: 'VoiceRecognition'); |
final bool result = await _channel.invokeMethod('initialize', { |
||||
_eventSubscription = _eventChannel.receiveBroadcastStream().listen( |
'subscriptionKey': _subscriptionKey, |
||||
(dynamic event) { |
'serviceRegion': _serviceRegion, |
||||
_handleEvent(event.toString()); |
}); |
||||
}, |
_isInitialized = result; |
||||
onError: (dynamic error) { |
return result; |
||||
developer.log('VoiceRecognitionService: 事件通道错误: $error', name: 'VoiceRecognition'); |
} on PlatformException catch (e) { |
||||
if (onError != null) { |
print('语音识别初始化失败: ${e.message}'); |
||||
onError!('事件通道错误: $error'); |
_isInitialized = false; |
||||
} |
throw e; |
||||
}, |
|
||||
); |
|
||||
} catch (e, stack) { |
|
||||
developer.log('VoiceRecognitionService: 设置事件监听器失败: $e', name: 'VoiceRecognition'); |
|
||||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
/// 处理来自原生层的事件 |
/// 执行一次性语音识别 |
||||
void _handleEvent(String eventJson) { |
/// |
||||
|
/// 返回识别的文本,如果识别失败则抛出 PlatformException |
||||
|
Future<String> recognizeSpeech() async { |
||||
|
if (!_isInitialized) { |
||||
|
throw Exception('语音识别服务未初始化,请先调用 initialize()'); |
||||
|
} |
||||
|
|
||||
try { |
try { |
||||
developer.log('VoiceRecognitionService: 收到原始事件数据: $eventJson', name: 'VoiceRecognition'); |
final String result = await _channel.invokeMethod('recognizeOnce'); |
||||
|
return result; |
||||
final eventData = jsonDecode(eventJson); |
} on PlatformException catch (e) { |
||||
final eventName = eventData['event']; |
print('语音识别失败: ${e.message}'); |
||||
final data = eventData['data']; |
throw e; |
||||
|
|
||||
developer.log('VoiceRecognitionService: 收到事件: $eventName, 数据: $data', name: 'VoiceRecognition'); |
|
||||
|
|
||||
switch (eventName) { |
|
||||
case 'status': |
|
||||
_handleStatusEvent(data.toString()); |
|
||||
break; |
|
||||
case 'result': |
|
||||
_handleResultEvent(data.toString()); |
|
||||
break; |
|
||||
case 'error': |
|
||||
_handleErrorEvent(data.toString()); |
|
||||
break; |
|
||||
case 'vad': |
|
||||
_handleVadEvent(data.toString()); |
|
||||
break; |
|
||||
default: |
|
||||
developer.log('VoiceRecognitionService: 未知事件类型: $eventName', name: 'VoiceRecognition'); |
|
||||
break; |
|
||||
} |
|
||||
} catch (e, stack) { |
|
||||
developer.log('VoiceRecognitionService: 处理事件失败: $e', name: 'VoiceRecognition'); |
|
||||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
/// 处理状态事件 |
/// 开始连续语音识别 |
||||
void _handleStatusEvent(String status) { |
/// |
||||
developer.log('VoiceRecognitionService: 状态变更: $status', name: 'VoiceRecognition'); |
/// 返回一个包含识别事件的流,如果开始失败则抛出 PlatformException |
||||
|
Future<Stream<RecognitionEvent>> startContinuousRecognition() async { |
||||
switch (status) { |
if (!_isInitialized) { |
||||
case 'initialized': |
throw Exception('语音识别服务未初始化,请先调用 initialize()'); |
||||
isInitialized.value = true; |
|
||||
isConnecting.value = false; |
|
||||
developer.log('VoiceRecognitionService: 引擎初始化完成', name: 'VoiceRecognition'); |
|
||||
break; |
|
||||
case 'listening': |
|
||||
isListening.value = true; |
|
||||
developer.log('VoiceRecognitionService: 开始监听', name: 'VoiceRecognition'); |
|
||||
break; |
|
||||
case 'idle': |
|
||||
isListening.value = false; |
|
||||
developer.log('VoiceRecognitionService: 停止监听', name: 'VoiceRecognition'); |
|
||||
break; |
|
||||
} |
} |
||||
|
|
||||
if (onConnectionStatusChanged != null) { |
if (_isContinuousRecognitionActive) { |
||||
onConnectionStatusChanged!(status); |
throw Exception('连续语音识别已经在进行中'); |
||||
} |
} |
||||
} |
|
||||
|
|
||||
/// 处理识别结果事件 |
|
||||
void _handleResultEvent(String resultJson) { |
|
||||
try { |
try { |
||||
final resultData = jsonDecode(resultJson); |
// 创建事件流控制器 |
||||
final text = resultData['text']; |
_eventStreamController = StreamController<RecognitionEvent>.broadcast(); |
||||
final isFinal = resultData['isFinal']; |
|
||||
|
|
||||
developer.log('VoiceRecognitionService: 识别结果: $text, 是否最终结果: $isFinal', name: 'VoiceRecognition'); |
// 设置事件监听 |
||||
|
_eventSubscription = _eventChannel |
||||
|
.receiveBroadcastStream() |
||||
|
.listen(_handleRecognitionEvent, onError: _handleRecognitionError); |
||||
|
|
||||
// 更新识别文本 |
// 开始连续识别 |
||||
recognizedText.value = text; |
final bool result = await _channel.invokeMethod('startContinuousRecognition'); |
||||
|
_isContinuousRecognitionActive = result; |
||||
|
|
||||
// 调用回调函数 |
// 设置公开的流 |
||||
if (onRecognitionResult != null) { |
_recognitionStream = _eventStreamController!.stream; |
||||
onRecognitionResult!(text); |
|
||||
} |
|
||||
|
|
||||
// 如果是最终结果,调用完成回调 |
return _recognitionStream!; |
||||
if (isFinal && text.isNotEmpty) { |
} on PlatformException catch (e) { |
||||
developer.log('VoiceRecognitionService: 句子识别完成: $text', name: 'VoiceRecognition'); |
print('开始连续语音识别失败: ${e.message}'); |
||||
|
_cleanupEventStream(); |
||||
if (onSentenceComplete != null) { |
throw e; |
||||
onSentenceComplete!(text); |
|
||||
} |
|
||||
|
|
||||
if (onRecognitionComplete != null) { |
|
||||
onRecognitionComplete!(); |
|
||||
} |
|
||||
} |
|
||||
} catch (e, stack) { |
|
||||
developer.log('VoiceRecognitionService: 处理识别结果失败: $e', name: 'VoiceRecognition'); |
|
||||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
/// 处理错误事件 |
/// 停止连续语音识别 |
||||
void _handleErrorEvent(String errorJson) { |
/// |
||||
try { |
/// 返回 true 表示停止成功,否则抛出 PlatformException |
||||
final errorData = jsonDecode(errorJson); |
Future<bool> stopContinuousRecognition() async { |
||||
final errorMessage = errorData['message']; |
if (!_isInitialized) { |
||||
final errorCode = errorData['code'] ?? 'unknown'; |
throw Exception('语音识别服务未初始化,请先调用 initialize()'); |
||||
|
|
||||
developer.log('VoiceRecognitionService: 语音识别错误: 代码=$errorCode, 消息=$errorMessage', name: 'VoiceRecognition', error: errorMessage); |
|
||||
|
|
||||
if (onError != null) { |
|
||||
onError!(errorMessage); |
|
||||
} |
|
||||
} catch (e, stack) { |
|
||||
developer.log('VoiceRecognitionService: 处理错误事件失败: $e', name: 'VoiceRecognition'); |
|
||||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|
||||
} |
} |
||||
} |
|
||||
|
if (!_isContinuousRecognitionActive) { |
||||
/// 处理 VAD 事件 |
return true; // 已经停止,直接返回成功 |
||||
void _handleVadEvent(String vadState) { |
} |
||||
// 可以根据需要处理 VAD 状态变化 |
|
||||
developer.log('VoiceRecognitionService: VAD 状态: $vadState', name: 'VoiceRecognition'); |
|
||||
} |
|
||||
|
|
||||
/// 检查引擎能力和支持的指令 |
|
||||
Future<void> checkEngineCapabilities() async { |
|
||||
try { |
try { |
||||
developer.log('VoiceRecognitionService: 检查引擎能力', name: 'VoiceRecognition'); |
final bool result = await _channel.invokeMethod('stopContinuousRecognition'); |
||||
|
_isContinuousRecognitionActive = !result; |
||||
// 检查是否已初始化 |
|
||||
if (!isInitialized.value) { |
|
||||
developer.log('VoiceRecognitionService: 引擎未初始化,尝试初始化', name: 'VoiceRecognition'); |
|
||||
await initialize(); |
|
||||
} |
|
||||
|
|
||||
// 调用原生方法检查引擎能力 |
// 清理事件流 |
||||
final result = await _methodChannel.invokeMethod<String>('checkEngineCapabilities'); |
_cleanupEventStream(); |
||||
developer.log('VoiceRecognitionService: 引擎能力检查结果: $result', name: 'VoiceRecognition'); |
|
||||
|
|
||||
if (result != null) { |
return result; |
||||
try { |
} on PlatformException catch (e) { |
||||
final capabilities = jsonDecode(result); |
print('停止连续语音识别失败: ${e.message}'); |
||||
developer.log('VoiceRecognitionService: 引擎版本: ${capabilities['version']}', name: 'VoiceRecognition'); |
throw e; |
||||
|
|
||||
if (capabilities['supported_params'] != null) { |
|
||||
developer.log('VoiceRecognitionService: 支持的参数: ${capabilities['supported_params']}', name: 'VoiceRecognition'); |
|
||||
} |
|
||||
|
|
||||
if (capabilities['supported_directives'] != null) { |
|
||||
developer.log('VoiceRecognitionService: 支持的指令: ${capabilities['supported_directives']}', name: 'VoiceRecognition'); |
|
||||
} |
|
||||
} catch (e) { |
|
||||
developer.log('VoiceRecognitionService: 解析能力结果失败: $e', name: 'VoiceRecognition'); |
|
||||
} |
|
||||
} |
|
||||
} catch (e, stack) { |
|
||||
developer.log('VoiceRecognitionService: 检查引擎能力失败: $e', name: 'VoiceRecognition', error: e); |
|
||||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
/// 初始化语音识别引擎 |
/// 检查连续识别是否活跃 |
||||
Future<void> initialize() async { |
bool isContinuousRecognitionActive() { |
||||
try { |
return _isContinuousRecognitionActive; |
||||
developer.log('VoiceRecognitionService: 开始初始化语音引擎', name: 'VoiceRecognition'); |
} |
||||
isConnecting.value = true; |
|
||||
|
/// 处理来自原生端的识别事件 |
||||
// 检查凭证 |
void _handleRecognitionEvent(dynamic event) { |
||||
if (_appKey.isEmpty || _accessKey.isEmpty) { |
if (event is! Map) return; |
||||
developer.log('VoiceRecognitionService: 凭证未设置,无法初始化', name: 'VoiceRecognition', error: '火山语音识别凭证未设置'); |
|
||||
throw '火山语音识别凭证未设置'; |
final Map<dynamic, dynamic> eventMap = event; |
||||
} |
final String eventType = eventMap['eventType'] as String? ?? ''; |
||||
|
|
||||
// 添加详细日志 |
switch (eventType) { |
||||
developer.log('VoiceRecognitionService: 使用凭证 AppKey: $_appKey, AccessKey: ${_accessKey.substring(0, 4)}***, Cluster: $_cluster', name: 'VoiceRecognition'); |
case 'finalResult': |
||||
|
final String text = eventMap['text'] as String? ?? ''; |
||||
// 调用原生方法初始化引擎 |
_latestRecognizedText = text; |
||||
try { |
_eventStreamController?.add(RecognitionEvent( |
||||
final result = await _methodChannel.invokeMethod<String>( |
type: RecognitionEventType.finalResult, |
||||
'initialize', |
text: text, |
||||
{ |
)); |
||||
'appKey': _appKey, |
break; |
||||
'accessKey': _accessKey, |
|
||||
'cluster': _cluster, |
|
||||
}, |
|
||||
); |
|
||||
|
|
||||
developer.log('VoiceRecognitionService: 初始化结果: $result', name: 'VoiceRecognition'); |
case 'intermediateResult': |
||||
|
final String text = eventMap['text'] as String? ?? ''; |
||||
|
_eventStreamController?.add(RecognitionEvent( |
||||
|
type: RecognitionEventType.intermediateResult, |
||||
|
text: text, |
||||
|
)); |
||||
|
break; |
||||
|
|
||||
// 初始化成功后,检查引擎能力 |
case 'sessionStarted': |
||||
await checkEngineCapabilities(); |
_eventStreamController?.add(RecognitionEvent( |
||||
} catch (e) { |
type: RecognitionEventType.sessionStarted, |
||||
developer.log('VoiceRecognitionService: 初始化方法调用失败: $e', name: 'VoiceRecognition', error: e); |
)); |
||||
|
break; |
||||
|
|
||||
// 尝试检查引擎是否已初始化 |
case 'sessionStopped': |
||||
try { |
_isContinuousRecognitionActive = false; |
||||
final isEngineInitialized = await _methodChannel.invokeMethod<bool>('isInitialized') ?? false; |
_eventStreamController?.add(RecognitionEvent( |
||||
if (isEngineInitialized) { |
type: RecognitionEventType.sessionStopped, |
||||
developer.log('VoiceRecognitionService: 引擎已经初始化,忽略错误', name: 'VoiceRecognition'); |
)); |
||||
isInitialized.value = true; |
break; |
||||
isConnecting.value = false; |
|
||||
|
|
||||
// 引擎已初始化,检查引擎能力 |
|
||||
await checkEngineCapabilities(); |
|
||||
return; |
|
||||
} |
|
||||
} catch (checkError) { |
|
||||
developer.log('VoiceRecognitionService: 检查引擎初始化状态失败: $checkError', name: 'VoiceRecognition'); |
|
||||
} |
|
||||
|
|
||||
// 如果检查失败或引擎未初始化,则重新抛出异常 |
case 'canceled': |
||||
rethrow; |
_isContinuousRecognitionActive = false; |
||||
} |
final String reason = eventMap['reason'] as String? ?? ''; |
||||
|
final String errorDetails = eventMap['errorDetails'] as String? ?? ''; |
||||
// 初始化成功后,状态会通过事件通道更新 |
_eventStreamController?.add(RecognitionEvent( |
||||
} catch (e, stack) { |
type: RecognitionEventType.canceled, |
||||
isConnecting.value = false; |
error: '$reason: $errorDetails', |
||||
developer.log('VoiceRecognitionService: 初始化语音识别引擎失败: $e', name: 'VoiceRecognition', error: e); |
)); |
||||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
break; |
||||
|
|
||||
String errorMessage = '初始化失败: $e'; |
case 'error': |
||||
|
final String error = eventMap['error'] as String? ?? ''; |
||||
// 提供更具体的错误信息 |
_eventStreamController?.add(RecognitionEvent( |
||||
if (e.toString().contains('error code: -7')) { |
type: RecognitionEventType.error, |
||||
errorMessage = '初始化失败: 无法创建日志目录,请检查应用权限'; |
error: error, |
||||
developer.log('VoiceRecognitionService: 错误代码 -7: 无法创建日志目录', name: 'VoiceRecognition'); |
)); |
||||
} else if (e.toString().contains('INIT_ERROR')) { |
break; |
||||
errorMessage = '初始化失败: 引擎初始化错误,请检查网络连接和凭证'; |
|
||||
developer.log('VoiceRecognitionService: INIT_ERROR: 引擎初始化错误', name: 'VoiceRecognition'); |
|
||||
} |
|
||||
|
|
||||
if (onError != null) { |
|
||||
onError!(errorMessage); |
|
||||
} |
|
||||
rethrow; |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
/// 开始语音识别 |
/// 处理识别事件流错误 |
||||
Future<void> startRecognition() async { |
void _handleRecognitionError(Object error) { |
||||
try { |
_eventStreamController?.addError(error); |
||||
developer.log('VoiceRecognitionService: 开始语音识别', name: 'VoiceRecognition'); |
_cleanupEventStream(); |
||||
|
|
||||
// 检查是否已初始化 |
|
||||
if (!isInitialized.value) { |
|
||||
developer.log('VoiceRecognitionService: 引擎未初始化,尝试初始化', name: 'VoiceRecognition'); |
|
||||
await initialize(); |
|
||||
} |
|
||||
|
|
||||
developer.log('VoiceRecognitionService: 调用原生方法 startRecognition', name: 'VoiceRecognition'); |
|
||||
|
|
||||
// 调用原生方法开始识别 |
|
||||
final result = await _methodChannel.invokeMethod<String>('startRecognition'); |
|
||||
developer.log('VoiceRecognitionService: 开始识别结果: $result', name: 'VoiceRecognition'); |
|
||||
|
|
||||
// 状态会通过事件通道更新 |
|
||||
} catch (e, stack) { |
|
||||
developer.log('VoiceRecognitionService: 开始语音识别失败: $e', name: 'VoiceRecognition', error: e); |
|
||||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|
||||
|
|
||||
if (onError != null) { |
|
||||
onError!('开始识别失败: $e'); |
|
||||
} |
|
||||
rethrow; |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
/// 停止语音识别 |
/// 清理事件流资源 |
||||
Future<void> stopRecognition() async { |
void _cleanupEventStream() { |
||||
|
_eventSubscription?.cancel(); |
||||
|
_eventSubscription = null; |
||||
|
|
||||
|
_eventStreamController?.close(); |
||||
|
_eventStreamController = null; |
||||
|
|
||||
|
_recognitionStream = null; |
||||
|
_isContinuousRecognitionActive = false; |
||||
|
} |
||||
|
|
||||
|
/// 释放资源 |
||||
|
Future<void> dispose() async { |
||||
try { |
try { |
||||
developer.log('VoiceRecognitionService: 停止语音识别', name: 'VoiceRecognition'); |
if (_isContinuousRecognitionActive) { |
||||
|
await stopContinuousRecognition(); |
||||
// 检查是否已初始化 |
|
||||
if (!isInitialized.value) { |
|
||||
developer.log('VoiceRecognitionService: 引擎未初始化,无法停止识别', name: 'VoiceRecognition'); |
|
||||
return; |
|
||||
} |
} |
||||
|
|
||||
developer.log('VoiceRecognitionService: 调用原生方法 stopRecognition', name: 'VoiceRecognition'); |
await _channel.invokeMethod('dispose'); |
||||
|
_cleanupEventStream(); |
||||
// 调用原生方法停止识别 |
_isInitialized = false; |
||||
final result = await _methodChannel.invokeMethod<String>('stopRecognition'); |
} catch (e) { |
||||
developer.log('VoiceRecognitionService: 停止识别结果: $result', name: 'VoiceRecognition'); |
print('释放语音识别资源失败: $e'); |
||||
|
|
||||
// 状态会通过事件通道更新 |
|
||||
} catch (e, stack) { |
|
||||
developer.log('VoiceRecognitionService: 停止语音识别失败: $e', name: 'VoiceRecognition', error: e); |
|
||||
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition'); |
|
||||
|
|
||||
if (onError != null) { |
|
||||
onError!('停止识别失败: $e'); |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
} |
||||
|
|
||||
|
/// 识别事件类型 |
||||
|
enum RecognitionEventType { |
||||
|
/// 最终识别结果 |
||||
|
finalResult, |
||||
|
|
||||
|
/// 中间识别结果(实时反馈) |
||||
|
intermediateResult, |
||||
|
|
||||
|
/// 会话开始 |
||||
|
sessionStarted, |
||||
|
|
||||
|
/// 会话结束 |
||||
|
sessionStopped, |
||||
|
|
||||
|
/// 识别取消 |
||||
|
canceled, |
||||
|
|
||||
|
/// 识别错误 |
||||
|
error, |
||||
|
} |
||||
|
|
||||
|
/// 识别事件 |
||||
|
class RecognitionEvent { |
||||
|
/// 事件类型 |
||||
|
final RecognitionEventType type; |
||||
|
|
||||
|
/// 识别文本(仅在 finalResult 和 intermediateResult 类型中有效) |
||||
|
final String text; |
||||
|
|
||||
|
/// 错误信息(仅在 error 和 canceled 类型中有效) |
||||
|
final String error; |
||||
|
|
||||
|
RecognitionEvent({ |
||||
|
required this.type, |
||||
|
this.text = '', |
||||
|
this.error = '', |
||||
|
}); |
||||
|
|
||||
@override |
@override |
||||
void onClose() { |
String toString() { |
||||
developer.log('VoiceRecognitionService: 关闭服务', name: 'VoiceRecognition'); |
return 'RecognitionEvent{type: $type, text: $text, error: $error}'; |
||||
|
|
||||
// 取消事件订阅 |
|
||||
_eventSubscription?.cancel(); |
|
||||
|
|
||||
// 停止识别 |
|
||||
stopRecognition(); |
|
||||
|
|
||||
super.onClose(); |
|
||||
} |
} |
||||
} |
} |
||||
Loading…
Reference in new issue