Browse Source

集成vol语音失败

weicu
wolfplus 2 years ago
parent
commit
f0ed19f5e1
  1. 6
      android/app/build.gradle.kts
  2. 11
      android/app/src/main/AndroidManifest.xml
  3. 89
      android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt
  4. 320
      android/app/src/main/kotlin/com/example/deep_voice/VolcSpeechBridge.kt
  5. 13
      android/app/src/main/res/xml/network_security_config.xml
  6. 9
      android/settings.gradle.kts
  7. 189
      lib/data/services/voice_recognition_service.dart
  8. 10
      lib/modules/profile/views/profile_view.dart
  9. 559
      lib/modules/speech_demo/speech_demo_page.dart

6
android/app/build.gradle.kts

@ -1,4 +1,8 @@
repositories {
google()
mavenCentral()
maven { url = uri("https://artifact.bytedance.com/repository/Volcengine/") }
}
plugins {
id("com.android.application")

11
android/app/src/main/AndroidManifest.xml

@ -9,11 +9,22 @@
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<!-- 添加存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 添加Android 11+的存储权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<application
android:label="deep_voice"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
android:enableOnBackInvokedCallback="true"
tools:replace="android:label">
<!-- Audio Service -->

89
android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt

@ -1,18 +1,107 @@
package com.example.deep_voice
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.util.Log
import com.ryanheise.audioservice.AudioServiceActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: AudioServiceActivity() {
private var volcSpeechBridge: VolcSpeechBridge? = null
private val TAG = "MainActivity"
private val MANAGE_STORAGE_REQUEST_CODE = 2002
private lateinit var permissionChannel: MethodChannel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.i(TAG, "MainActivity onCreate")
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 设置权限通道 - 只处理Android 11+的MANAGE_EXTERNAL_STORAGE权限
permissionChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.deep_voice/permissions")
permissionChannel.setMethodCallHandler { call, result ->
when (call.method) {
"openAppSettings" -> {
openAppSettings()
result.success(true)
}
"requestManageExternalStorage" -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
requestManageExternalStorage()
result.success(true)
} else {
result.success(false)
}
}
"isExternalStorageManager" -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
result.success(Environment.isExternalStorageManager())
} else {
result.success(true) // 在Android 11以下版本,返回true
}
}
else -> result.notImplemented()
}
}
// 准备火山语音识别环境
try {
Log.i(TAG, "Preparing Volcano Speech Engine environment")
com.bytedance.speech.speechengine.SpeechEngineGenerator.PrepareEnvironment(applicationContext, application)
Log.i(TAG, "Volcano Speech Engine environment prepared successfully")
} catch (e: Exception) {
Log.e(TAG, "Failed to prepare Volcano Speech Engine environment", e)
}
// 初始化火山语音识别桥接类
volcSpeechBridge = VolcSpeechBridge(this, flutterEngine.dartExecutor.binaryMessenger)
}
private fun requestManageExternalStorage() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
Log.i(TAG, "Requesting MANAGE_EXTERNAL_STORAGE permission")
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
intent.data = Uri.parse("package:$packageName")
startActivityForResult(intent, MANAGE_STORAGE_REQUEST_CODE)
} catch (e: Exception) {
Log.e(TAG, "Error requesting MANAGE_EXTERNAL_STORAGE", e)
// 如果上面的Intent失败,尝试打开通用的存储设置页面
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
startActivityForResult(intent, MANAGE_STORAGE_REQUEST_CODE)
}
}
}
private fun openAppSettings() {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:$packageName")
startActivity(intent)
}
// 只处理MANAGE_EXTERNAL_STORAGE权限的结果
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == MANAGE_STORAGE_REQUEST_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val granted = Environment.isExternalStorageManager()
Log.i(TAG, "MANAGE_EXTERNAL_STORAGE permission: ${if (granted) "granted" else "denied"}")
// 通知Flutter层权限状态变化
permissionChannel.invokeMethod("manageExternalStorageResult", granted)
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onDestroy() {
// 释放火山语音识别资源
volcSpeechBridge?.dispose()

320
android/app/src/main/kotlin/com/example/deep_voice/VolcSpeechBridge.kt

@ -11,6 +11,7 @@ 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
@ -27,13 +28,10 @@ class VolcSpeechBridge(
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 const val DIRECTIVE_START_RECOGNITION = 1
private const val DIRECTIVE_STOP_RECOGNITION = 2
}
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
@ -46,7 +44,8 @@ class VolcSpeechBridge(
"initialize" -> {
val appKey = call.argument<String>("appKey") ?: ""
val accessKey = call.argument<String>("accessKey") ?: ""
initialize(appKey, accessKey, result)
val cluster = call.argument<String>("cluster") ?: "volcano_asr"
initialize(appKey, accessKey, cluster, result)
}
"startRecognition" -> {
startRecognition(result)
@ -57,6 +56,9 @@ class VolcSpeechBridge(
"isInitialized" -> {
result.success(isInitialized)
}
"checkEngineCapabilities" -> {
checkEngineCapabilities(result)
}
else -> {
result.notImplemented()
}
@ -78,32 +80,75 @@ class VolcSpeechBridge(
/**
* 初始化火山语音识别引擎
*/
fun initialize(appKey: String, accessKey: String, result: Result) {
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()
engine?.createEngine()
engineHandler = engine?.createEngine() ?: 0
// 设置监听器
engine?.setListener(this)
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("app_id", appKey)
engine?.setOptionString("access_key", accessKey)
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)
// 设置语音识别参数
engine?.setOptionBoolean("enable_punctuation", true) // 启用标点符号
engine?.setOptionBoolean("enable_intermediate_result", true) // 启用中间结果
engine?.setOptionBoolean("enable_vad", true) // 启用语音活动检测
// 设置最大录音时长(60秒)
engine?.setOptionInt(engineHandler, SpeechEngineDefines.PARAMS_KEY_VAD_MAX_SPEECH_DURATION_INT, 60000)
// 设置语言(普通话)
engine?.setOptionString("language", "zh")
// 控制识别效果
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)
// 初始化引擎
val ret = engine?.initEngine()
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")
@ -127,17 +172,24 @@ class VolcSpeechBridge(
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")
val ret = engine?.sendDirective(DIRECTIVE_START_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")
// 发送状态更新到 Flutter
sendEvent("status", "listening")
// 状态会通过回调更新
} else {
Log.e(TAG, "Start recognition failed with error code: $ret")
result.error("START_ERROR", "Start recognition failed with error code: $ret", null)
@ -154,20 +206,28 @@ class VolcSpeechBridge(
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 ret = engine?.sendDirective(DIRECTIVE_STOP_RECOGNITION, "")
if (ret == SpeechEngineDefines.ERR_NO_ERROR) {
// 先发送音频输入完成指令
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")
// 发送状态更新到 Flutter
sendEvent("status", "idle")
// 状态会通过回调更新
} else {
Log.e(TAG, "Stop recognition failed with error code: $ret")
result.error("STOP_ERROR", "Stop recognition failed with error code: $ret", null)
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)
@ -180,7 +240,10 @@ class VolcSpeechBridge(
*/
fun dispose() {
try {
engine?.destroyEngine()
if (engineHandler != 0L) {
engine?.destroyEngine(engineHandler)
engineHandler = 0
}
engine = null
isInitialized = false
Log.i(TAG, "Volcengine Speech Engine released")
@ -216,36 +279,60 @@ class VolcSpeechBridge(
*/
override fun onSpeechMessage(messageType: Int, messageData: ByteArray?, messageLength: Int) {
try {
when (messageType) {
// 初始化完成消息
1 -> {
Log.i(TAG, "Engine initialized successfully")
isInitialized = true
Log.d(TAG, "Received speech message type: $messageType, length: $messageLength")
// 通知 Flutter 初始化完成
methodResult?.success("Engine initialized")
methodResult = null
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")
}
// 发送状态更新到 Flutter
sendEvent("status", "initialized")
when (messageType) {
// 引擎启动成功
SpeechEngineDefines.MESSAGE_TYPE_ENGINE_START -> {
Log.i(TAG, "Engine started successfully")
sendEvent("status", "listening")
}
// 错误消息
2 -> {
val errorCode = messageData?.let { String(it, 0, messageLength).toIntOrNull() } ?: -1
Log.e(TAG, "Engine error: $errorCode")
// 引擎关闭
SpeechEngineDefines.MESSAGE_TYPE_ENGINE_STOP -> {
Log.i(TAG, "Engine stopped")
sendEvent("status", "idle")
}
// 通知 Flutter 识别出错
val errorData = JSONObject().apply {
put("code", errorCode)
put("message", "Error code: $errorCode")
}
// 错误信息
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())
sendEvent("error", errorData.toString())
} catch (e: Exception) {
Log.e(TAG, "Error parsing error JSON", e)
sendEvent("error", "Error code: Unknown")
}
}
}
// VAD 状态变化消息
3 -> {
SpeechEngineDefines.MESSAGE_TYPE_VAD_STATE -> {
val state = messageData?.let { String(it, 0, messageLength).toIntOrNull() } ?: -1
Log.i(TAG, "VAD state changed: $state")
@ -258,36 +345,149 @@ class VolcSpeechBridge(
sendEvent("vad", vadState)
}
// 识别结果消息
4 -> {
// 中间识别结果
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)
val text = jsonObject.optString("text", "")
val isFinal = jsonObject.optBoolean("is_final", false)
Log.i(TAG, "Recognition result: $text, isFinal: $isFinal")
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().apply {
put("text", text)
put("isFinal", isFinal)
// 发送识别结果到 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)
}
}
}
sendEvent("result", resultData.toString())
// 最终识别结果
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 result JSON", e)
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)
}
}
}

13
android/app/src/main/res/xml/network_security_config.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">127.0.0.1</domain>
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>

9
android/settings.gradle.kts

@ -25,13 +25,4 @@ plugins {
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://artifact.bytedance.com/repository/Volcengine/") }
gradlePluginPortal()
}
}
include(":app")

189
lib/data/services/voice_recognition_service.dart

@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'dart:developer' as developer;
/// 火山语音识别服务
/// 负责与原生层的火山语音识别引擎交互
@ -37,9 +38,11 @@ class VoiceRecognitionService extends GetxService {
// 火山语音识别的 AppKey 和 AccessKey
late final String _appKey;
late final String _accessKey;
late final String _cluster;
@override
void onInit() {
developer.log('VoiceRecognitionService: 初始化服务', name: 'VoiceRecognition');
super.onInit();
_loadCredentials();
_setupEventListener();
@ -50,41 +53,51 @@ class VoiceRecognitionService extends GetxService {
try {
_appKey = dotenv.env['VOLC_SPEECH_APP_KEY'] ?? '';
_accessKey = dotenv.env['VOLC_SPEECH_ACCESS_KEY'] ?? '';
_cluster = dotenv.env['VOLC_SPEECH_CLUSTER'] ?? 'volcano_asr';
developer.log('VoiceRecognitionService: 加载凭证 - AppKey: ${_appKey.isNotEmpty ? '已设置' : '未设置'}, AccessKey: ${_accessKey.isNotEmpty ? '已设置' : '未设置'}, Cluster: $_cluster', name: 'VoiceRecognition');
if (_appKey.isEmpty || _accessKey.isEmpty) {
debugPrint('警告: 火山语音识别凭证未设置,请在 .env 文件中设置 VOLC_SPEECH_APP_KEY 和 VOLC_SPEECH_ACCESS_KEY');
developer.log('VoiceRecognitionService: 警告 - 火山语音识别凭证未设置,请在 .env 文件中设置 VOLC_SPEECH_APP_KEY 和 VOLC_SPEECH_ACCESS_KEY', name: 'VoiceRecognition');
}
} catch (e) {
debugPrint('加载火山语音识别凭证失败: $e');
} catch (e, stack) {
developer.log('VoiceRecognitionService: 加载火山语音识别凭证失败: $e', name: 'VoiceRecognition');
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
}
}
/// 设置事件监听器
void _setupEventListener() {
try {
developer.log('VoiceRecognitionService: 设置事件监听器', name: 'VoiceRecognition');
_eventSubscription = _eventChannel.receiveBroadcastStream().listen(
(dynamic event) {
_handleEvent(event.toString());
},
onError: (dynamic error) {
debugPrint('事件通道错误: $error');
developer.log('VoiceRecognitionService: 事件通道错误: $error', name: 'VoiceRecognition');
if (onError != null) {
onError!('事件通道错误: $error');
}
},
);
} catch (e) {
debugPrint('设置事件监听器失败: $e');
} catch (e, stack) {
developer.log('VoiceRecognitionService: 设置事件监听器失败: $e', name: 'VoiceRecognition');
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
}
}
/// 处理来自原生层的事件
void _handleEvent(String eventJson) {
try {
developer.log('VoiceRecognitionService: 收到原始事件数据: $eventJson', name: 'VoiceRecognition');
final eventData = jsonDecode(eventJson);
final eventName = eventData['event'];
final data = eventData['data'];
developer.log('VoiceRecognitionService: 收到事件: $eventName, 数据: $data', name: 'VoiceRecognition');
switch (eventName) {
case 'status':
_handleStatusEvent(data.toString());
@ -98,24 +111,33 @@ class VoiceRecognitionService extends GetxService {
case 'vad':
_handleVadEvent(data.toString());
break;
default:
developer.log('VoiceRecognitionService: 未知事件类型: $eventName', name: 'VoiceRecognition');
break;
}
} catch (e) {
debugPrint('处理事件失败: $e');
} 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');
switch (status) {
case 'initialized':
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;
}
@ -131,6 +153,8 @@ class VoiceRecognitionService extends GetxService {
final text = resultData['text'];
final isFinal = resultData['isFinal'];
developer.log('VoiceRecognitionService: 识别结果: $text, 是否最终结果: $isFinal', name: 'VoiceRecognition');
// 更新识别文本
recognizedText.value = text;
@ -141,6 +165,8 @@ class VoiceRecognitionService extends GetxService {
// 如果是最终结果,调用完成回调
if (isFinal && text.isNotEmpty) {
developer.log('VoiceRecognitionService: 句子识别完成: $text', name: 'VoiceRecognition');
if (onSentenceComplete != null) {
onSentenceComplete!(text);
}
@ -149,8 +175,9 @@ class VoiceRecognitionService extends GetxService {
onRecognitionComplete!();
}
}
} catch (e) {
debugPrint('处理识别结果失败: $e');
} catch (e, stack) {
developer.log('VoiceRecognitionService: 处理识别结果失败: $e', name: 'VoiceRecognition');
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
}
}
@ -159,50 +186,134 @@ class VoiceRecognitionService extends GetxService {
try {
final errorData = jsonDecode(errorJson);
final errorMessage = errorData['message'];
final errorCode = errorData['code'] ?? 'unknown';
debugPrint('语音识别错误: $errorMessage');
developer.log('VoiceRecognitionService: 语音识别错误: 代码=$errorCode, 消息=$errorMessage', name: 'VoiceRecognition', error: errorMessage);
if (onError != null) {
onError!(errorMessage);
}
} catch (e) {
debugPrint('处理错误事件失败: $e');
} catch (e, stack) {
developer.log('VoiceRecognitionService: 处理错误事件失败: $e', name: 'VoiceRecognition');
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
}
}
/// 处理 VAD 事件
void _handleVadEvent(String vadState) {
// 可以根据需要处理 VAD 状态变化
debugPrint('VAD 状态: $vadState');
developer.log('VoiceRecognitionService: VAD 状态: $vadState', name: 'VoiceRecognition');
}
/// 检查引擎能力和支持的指令
Future<void> checkEngineCapabilities() async {
try {
developer.log('VoiceRecognitionService: 检查引擎能力', name: 'VoiceRecognition');
// 检查是否已初始化
if (!isInitialized.value) {
developer.log('VoiceRecognitionService: 引擎未初始化,尝试初始化', name: 'VoiceRecognition');
await initialize();
}
// 调用原生方法检查引擎能力
final result = await _methodChannel.invokeMethod<String>('checkEngineCapabilities');
developer.log('VoiceRecognitionService: 引擎能力检查结果: $result', name: 'VoiceRecognition');
if (result != null) {
try {
final capabilities = jsonDecode(result);
developer.log('VoiceRecognitionService: 引擎版本: ${capabilities['version']}', name: 'VoiceRecognition');
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 {
try {
developer.log('VoiceRecognitionService: 开始初始化语音引擎', name: 'VoiceRecognition');
isConnecting.value = true;
// 检查凭证
if (_appKey.isEmpty || _accessKey.isEmpty) {
developer.log('VoiceRecognitionService: 凭证未设置,无法初始化', name: 'VoiceRecognition', error: '火山语音识别凭证未设置');
throw '火山语音识别凭证未设置';
}
// 添加详细日志
developer.log('VoiceRecognitionService: 使用凭证 AppKey: $_appKey, AccessKey: ${_accessKey.substring(0, 4)}***, Cluster: $_cluster', name: 'VoiceRecognition');
// 调用原生方法初始化引擎
final result = await _methodChannel.invokeMethod<String>(
'initialize',
{
'appKey': _appKey,
'accessKey': _accessKey,
},
);
try {
final result = await _methodChannel.invokeMethod<String>(
'initialize',
{
'appKey': _appKey,
'accessKey': _accessKey,
'cluster': _cluster,
},
);
developer.log('VoiceRecognitionService: 初始化结果: $result', name: 'VoiceRecognition');
// 初始化成功后,检查引擎能力
await checkEngineCapabilities();
} catch (e) {
developer.log('VoiceRecognitionService: 初始化方法调用失败: $e', name: 'VoiceRecognition', error: e);
// 尝试检查引擎是否已初始化
try {
final isEngineInitialized = await _methodChannel.invokeMethod<bool>('isInitialized') ?? false;
if (isEngineInitialized) {
developer.log('VoiceRecognitionService: 引擎已经初始化,忽略错误', name: 'VoiceRecognition');
isInitialized.value = true;
isConnecting.value = false;
// 引擎已初始化,检查引擎能力
await checkEngineCapabilities();
return;
}
} catch (checkError) {
developer.log('VoiceRecognitionService: 检查引擎初始化状态失败: $checkError', name: 'VoiceRecognition');
}
debugPrint('初始化结果: $result');
// 如果检查失败或引擎未初始化,则重新抛出异常
rethrow;
}
// 初始化成功后,状态会通过事件通道更新
} catch (e) {
} catch (e, stack) {
isConnecting.value = false;
debugPrint('初始化语音识别引擎失败: $e');
developer.log('VoiceRecognitionService: 初始化语音识别引擎失败: $e', name: 'VoiceRecognition', error: e);
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
String errorMessage = '初始化失败: $e';
// 提供更具体的错误信息
if (e.toString().contains('error code: -7')) {
errorMessage = '初始化失败: 无法创建日志目录,请检查应用权限';
developer.log('VoiceRecognitionService: 错误代码 -7: 无法创建日志目录', name: 'VoiceRecognition');
} else if (e.toString().contains('INIT_ERROR')) {
errorMessage = '初始化失败: 引擎初始化错误,请检查网络连接和凭证';
developer.log('VoiceRecognitionService: INIT_ERROR: 引擎初始化错误', name: 'VoiceRecognition');
}
if (onError != null) {
onError!('初始化失败: $e');
onError!(errorMessage);
}
rethrow;
}
@ -211,18 +322,25 @@ class VoiceRecognitionService extends GetxService {
/// 开始语音识别
Future<void> startRecognition() async {
try {
developer.log('VoiceRecognitionService: 开始语音识别', name: 'VoiceRecognition');
// 检查是否已初始化
if (!isInitialized.value) {
developer.log('VoiceRecognitionService: 引擎未初始化,尝试初始化', name: 'VoiceRecognition');
await initialize();
}
developer.log('VoiceRecognitionService: 调用原生方法 startRecognition', name: 'VoiceRecognition');
// 调用原生方法开始识别
final result = await _methodChannel.invokeMethod<String>('startRecognition');
debugPrint('开始识别结果: $result');
developer.log('VoiceRecognitionService: 开始识别结果: $result', name: 'VoiceRecognition');
// 状态会通过事件通道更新
} catch (e) {
debugPrint('开始语音识别失败: $e');
} catch (e, stack) {
developer.log('VoiceRecognitionService: 开始语音识别失败: $e', name: 'VoiceRecognition', error: e);
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
if (onError != null) {
onError!('开始识别失败: $e');
}
@ -233,18 +351,25 @@ class VoiceRecognitionService extends GetxService {
/// 停止语音识别
Future<void> stopRecognition() async {
try {
developer.log('VoiceRecognitionService: 停止语音识别', name: 'VoiceRecognition');
// 检查是否已初始化
if (!isInitialized.value) {
developer.log('VoiceRecognitionService: 引擎未初始化,无法停止识别', name: 'VoiceRecognition');
return;
}
developer.log('VoiceRecognitionService: 调用原生方法 stopRecognition', name: 'VoiceRecognition');
// 调用原生方法停止识别
final result = await _methodChannel.invokeMethod<String>('stopRecognition');
debugPrint('停止识别结果: $result');
developer.log('VoiceRecognitionService: 停止识别结果: $result', name: 'VoiceRecognition');
// 状态会通过事件通道更新
} catch (e) {
debugPrint('停止语音识别失败: $e');
} catch (e, stack) {
developer.log('VoiceRecognitionService: 停止语音识别失败: $e', name: 'VoiceRecognition', error: e);
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
if (onError != null) {
onError!('停止识别失败: $e');
}
@ -253,6 +378,8 @@ class VoiceRecognitionService extends GetxService {
@override
void onClose() {
developer.log('VoiceRecognitionService: 关闭服务', name: 'VoiceRecognition');
// 取消事件订阅
_eventSubscription?.cancel();

10
lib/modules/profile/views/profile_view.dart

@ -4,6 +4,7 @@ import '../controllers/profile_controller.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../data/services/volcano_tts_service.dart';
import '../../../core/widgets/common_bottom_nav.dart';
import '../../speech_demo/speech_demo_page.dart';
class ProfileView extends GetView<ProfileController> {
const ProfileView({Key? key}) : super(key: key);
@ -109,6 +110,15 @@ class ProfileView extends GetView<ProfileController> {
},
),
const Divider(),
_buildMenuItem(
title: '语音识别测试',
icon: Icons.mic,
subtitle: '测试语音识别功能',
onTap: () {
Get.to(() => const SpeechDemoPage());
},
),
const Divider(),
_buildMenuItem(
title: '关于',
icon: Icons.info_outline,

559
lib/modules/speech_demo/speech_demo_page.dart

@ -1,56 +1,406 @@
import 'dart:async';
import 'dart:developer' as developer;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import '../../data/services/voice_recognition_service.dart';
class SpeechDemoPage extends StatefulWidget {
const SpeechDemoPage({Key? key}) : super(key: key);
@override
_SpeechDemoPageState createState() => _SpeechDemoPageState();
State<SpeechDemoPage> createState() => _SpeechDemoPageState();
}
class _SpeechDemoPageState extends State<SpeechDemoPage> {
final VoiceRecognitionService _recognitionService = Get.find<VoiceRecognitionService>();
final RxBool _permissionsGranted = false.obs;
final RxBool _isInitializing = false.obs;
final RxBool _isRecognizing = false.obs;
final RxString _errorMessage = ''.obs;
final RxString _recognizedText = ''.obs;
final RxString _status = ''.obs;
// 添加原生权限通道 - 只用于处理Android 11+的MANAGE_EXTERNAL_STORAGE权限
static const MethodChannel _permissionChannel = MethodChannel('com.example.deep_voice/permissions');
@override
void initState() {
super.initState();
_initializeEngine();
_setupPermissionListener();
_checkPermissions();
}
@override
void dispose() {
_recognitionService.stopRecognition();
super.dispose();
}
// 设置权限监听器 - 只监听MANAGE_EXTERNAL_STORAGE权限结果
void _setupPermissionListener() {
_permissionChannel.setMethodCallHandler((call) async {
if (call.method == 'manageExternalStorageResult') {
developer.log('Received manageExternalStorageResult from native: ${call.arguments}');
// 更新权限状态
await _checkPermissions();
}
return null;
});
}
// 检查所有必要的权限
Future<void> _checkPermissions() async {
developer.log('Checking permissions');
try {
// 检查基本权限
final micStatus = await Permission.microphone.status;
final storageStatus = await Permission.storage.status;
developer.log('=== 详细权限状态 ===');
developer.log('麦克风权限: $micStatus');
developer.log('存储权限: $storageStatus');
// 对于Android 11+,检查MANAGE_EXTERNAL_STORAGE权限
bool manageStorageGranted = true;
if (Platform.isAndroid) {
if (await _isAndroid11OrHigher()) {
// 使用原生方法检查MANAGE_EXTERNAL_STORAGE权限
try {
manageStorageGranted = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
} catch (e) {
developer.log('Error checking MANAGE_EXTERNAL_STORAGE: $e');
manageStorageGranted = await Permission.manageExternalStorage.isGranted;
}
developer.log('管理所有文件权限: $manageStorageGranted');
}
}
// 记录其他权限状态
developer.log('读取外部存储权限: ${await Permission.audio.status}');
developer.log('写入外部存储权限: ${await Permission.photos.status}');
developer.log('=====================');
// 更新权限状态
final bool basicPermissionsGranted = micStatus.isGranted && storageStatus.isGranted;
// 修改逻辑:如果是Android 11+且已授予MANAGE_EXTERNAL_STORAGE权限,则忽略普通存储权限状态
bool effectiveStoragePermission = storageStatus.isGranted;
if (Platform.isAndroid && await _isAndroid11OrHigher() && manageStorageGranted) {
developer.log('已授予MANAGE_EXTERNAL_STORAGE权限,忽略普通存储权限状态');
effectiveStoragePermission = true;
}
_permissionsGranted.value = micStatus.isGranted && (effectiveStoragePermission || manageStorageGranted);
developer.log('所有权限已授予: ${_permissionsGranted.value}');
// 如果权限已授予,初始化引擎
if (_permissionsGranted.value && !_isInitializing.value) {
_initializeEngine();
} else if (!_permissionsGranted.value) {
_errorMessage.value = '需要麦克风和存储权限才能使用语音识别功能';
}
} catch (e) {
developer.log('检查权限时出错: $e');
_errorMessage.value = '检查权限时出错: $e';
}
}
// 检查是否为Android 11或更高版本
Future<bool> _isAndroid11OrHigher() async {
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
return androidInfo.version.sdkInt >= 30; // Android 11 is API 30
}
return false;
}
// 记录详细的权限状态
Future<void> _logPermissionDetails() async {
developer.log('=== 详细权限状态 ===');
developer.log('麦克风权限: ${await Permission.microphone.status}');
developer.log('存储权限: ${await Permission.storage.status}');
if (Platform.isAndroid) {
if (await _isAndroid11OrHigher()) {
bool manageStorageGranted = false;
try {
manageStorageGranted = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
} catch (e) {
developer.log('Error checking MANAGE_EXTERNAL_STORAGE: $e');
manageStorageGranted = await Permission.manageExternalStorage.isGranted;
}
developer.log('管理所有文件权限: $manageStorageGranted');
}
}
developer.log('读取外部存储权限: ${await Permission.audio.status}');
developer.log('写入外部存储权限: ${await Permission.photos.status}');
developer.log('=====================');
}
// 请求所有必要的权限
Future<void> _requestPermissions() async {
developer.log('请求权限');
try {
// 对于Android 11+,优先请求MANAGE_EXTERNAL_STORAGE权限
if (Platform.isAndroid && await _isAndroid11OrHigher()) {
bool isManager = false;
try {
isManager = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
} catch (e) {
developer.log('检查MANAGE_EXTERNAL_STORAGE时出错: $e');
isManager = await Permission.manageExternalStorage.isGranted;
}
if (!isManager) {
developer.log('请求MANAGE_EXTERNAL_STORAGE权限');
// 使用原生方法请求MANAGE_EXTERNAL_STORAGE权限
await _permissionChannel.invokeMethod('requestManageExternalStorage');
// 等待一段时间,让用户有机会授予权限
await Future.delayed(const Duration(seconds: 1));
// 再次检查权限状态
try {
isManager = await _permissionChannel.invokeMethod('isExternalStorageManager') ?? false;
developer.log('MANAGE_EXTERNAL_STORAGE权限状态: $isManager');
} catch (e) {
developer.log('检查MANAGE_EXTERNAL_STORAGE时出错: $e');
}
}
}
// 请求基本权限
Map<Permission, PermissionStatus> statuses = await [
Permission.microphone,
Permission.storage,
].request();
// 记录结果
statuses.forEach((permission, status) {
developer.log('权限 $permission: $status');
});
// 检查权限状态
await _checkPermissions();
} catch (e) {
developer.log('请求权限时出错: $e');
_errorMessage.value = '请求权限时出错: $e';
}
}
// 打开应用设置
Future<void> _openAppSettings() async {
developer.log('打开应用设置');
try {
// 使用permission_handler打开设置
final opened = await openAppSettings();
if (!opened) {
// 如果permission_handler失败,使用原生方法
await _permissionChannel.invokeMethod('openAppSettings');
}
} catch (e) {
developer.log('打开应用设置时出错: $e');
// 使用原生方法作为备选
try {
await _permissionChannel.invokeMethod('openAppSettings');
} catch (e2) {
developer.log('通过原生通道打开应用设置时出错: $e2');
}
}
}
// 初始化语音引擎
Future<void> _initializeEngine() async {
if (_isInitializing.value) return;
_isInitializing.value = true;
_errorMessage.value = '';
_status.value = '正在初始化语音引擎...';
try {
// 再次检查权限状态
await _checkPermissions();
if (!_permissionsGranted.value) {
developer.log('权限未授予,无法初始化引擎');
_errorMessage.value = '权限未授予,无法初始化语音引擎';
_isInitializing.value = false;
return;
}
// 记录权限详情
await _logPermissionDetails();
// 测试存储权限
String? availableDir = await _getAvailableStorageDirectory();
if (availableDir != null) {
developer.log('找到可用的存储目录: $availableDir');
} else {
developer.log('警告: 未找到可用的存储目录,可能会影响语音引擎初始化');
}
// 初始化语音引擎
developer.log('初始化语音识别引擎');
await _recognitionService.initialize();
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('初始化失败: $e')),
);
developer.log('语音识别引擎初始化成功');
_status.value = '语音引擎初始化成功,可以开始识别';
_isInitializing.value = false;
} catch (e, stackTrace) {
developer.log('初始化语音识别引擎时出错: $e');
developer.log('堆栈跟踪: $stackTrace');
// 解析错误代码
String errorDetail = '未知错误';
if (e is PlatformException) {
developer.log('PlatformException 代码: ${e.code}, 消息: ${e.message}');
if (e.code == 'INIT_ERROR') {
if (e.message?.contains('-7') == true) {
errorDetail = '无法创建日志目录 (错误码: -7),可能是存储权限问题';
} else {
errorDetail = '初始化错误: ${e.message}';
}
}
}
_errorMessage.value = '初始化语音引擎失败: $errorDetail';
_status.value = '初始化失败';
_isInitializing.value = false;
// 显示错误提示和重试选项
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('初始化失败: $errorDetail'),
action: SnackBarAction(
label: '重试',
onPressed: _initializeEngine,
),
duration: const Duration(seconds: 10),
),
);
}
}
Future<void> _startRecognition() async {
developer.log('开始识别');
_errorMessage.value = '';
_isRecognizing.value = true;
_status.value = '正在识别...';
try {
await _recognitionService.startRecognition();
// 设置监听器接收识别结果
_recognitionService.recognizedText.listen((text) {
_recognizedText.value = text;
});
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('启动识别失败: $e')),
);
}
developer.log('开始识别时出错: $e');
_errorMessage.value = '启动识别失败: $e';
_isRecognizing.value = false;
_status.value = '识别启动失败';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('启动识别失败: $e'),
duration: const Duration(seconds: 3),
),
);
}
}
Future<void> _stopRecognition() async {
developer.log('停止识别');
try {
await _recognitionService.stopRecognition();
_isRecognizing.value = false;
_status.value = '识别已停止';
// 保存最终识别结果
_recognizedText.value = _recognitionService.recognizedText.value;
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('停止识别失败: $e')),
);
developer.log('停止识别时出错: $e');
_errorMessage.value = '停止识别失败: $e';
_isRecognizing.value = false;
_status.value = '识别停止失败';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('停止识别失败: $e'),
duration: const Duration(seconds: 3),
),
);
}
}
// 获取可用的存储目录
Future<String?> _getAvailableStorageDirectory() async {
try {
List<Directory> directoriesToTry = [];
// 应用专用外部存储目录
if (Platform.isAndroid) {
directoriesToTry.add(Directory('/storage/emulated/0/Android/data/com.example.deep_voice/files'));
}
// 应用内部存储目录
try {
final appDir = await getApplicationDocumentsDirectory();
directoriesToTry.add(appDir);
} catch (e) {
developer.log('获取应用内部存储目录失败: $e');
}
// 应用缓存目录
try {
final cacheDir = await getTemporaryDirectory();
directoriesToTry.add(cacheDir);
} catch (e) {
developer.log('获取应用缓存目录失败: $e');
}
// 尝试每个目录
for (var directory in directoriesToTry) {
try {
developer.log('测试目录可用性: ${directory.path}');
if (!await directory.exists()) {
await directory.create(recursive: true);
}
final testFile = File('${directory.path}/test_write.tmp');
await testFile.writeAsString('Test write at ${DateTime.now()}');
await testFile.readAsString(); // 验证可读
await testFile.delete();
developer.log('找到可用目录: ${directory.path}');
return directory.path;
} catch (e) {
developer.log('目录 ${directory.path} 不可用: $e');
}
}
developer.log('未找到可用的存储目录');
return null;
} catch (e) {
developer.log('获取可用存储目录时出错: $e');
return null;
}
}
@ -58,76 +408,133 @@ class _SpeechDemoPageState extends State<SpeechDemoPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('火山语音识别演示'),
title: const Text('语音识别演示'),
),
body: Center(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 权限请求部分
Obx(() => !_permissionsGranted.value
? Card(
color: Colors.amber.shade100,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'需要麦克风和存储权限',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(height: 8),
const Text(
'注意: 在Android 11及以上版本,您需要在设置中手动授予"管理所有文件"权限',
style: TextStyle(
fontSize: 14,
),
),
const SizedBox(height: 16),
Wrap(
spacing: 8.0,
runSpacing: 8.0,
alignment: WrapAlignment.center,
children: [
ElevatedButton(
onPressed: _requestPermissions,
child: const Text('授予权限'),
),
ElevatedButton(
onPressed: _openAppSettings,
child: const Text('打开设置'),
),
ElevatedButton(
onPressed: _logPermissionDetails,
child: const Text('检查权限'),
),
ElevatedButton(
onPressed: _initializeEngine,
child: const Text('强制初始化'),
),
],
),
],
),
),
)
: const SizedBox.shrink()),
// 状态显示
Obx(() {
String status = '准备中';
if (_recognitionService.isConnecting.value) {
status = '连接中...';
} else if (_recognitionService.isInitialized.value) {
status = _recognitionService.isListening.value ? '正在听...' : '就绪';
}
return Text('状态: $status', style: const TextStyle(fontSize: 18));
}),
const SizedBox(height: 20),
// 识别结果显示
Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
width: double.infinity,
height: 200,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.all(16),
child: Obx(() {
return SingleChildScrollView(
Obx(() => _status.value.isNotEmpty
? Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
'状态: ${_status.value}',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
)
: const SizedBox.shrink()),
// 错误消息
Obx(() => _errorMessage.value.isNotEmpty
? Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
_recognitionService.recognizedText.value,
style: const TextStyle(fontSize: 16),
'错误: ${_errorMessage.value}',
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
);
}),
)
: const SizedBox.shrink()),
// 识别结果显示区域
Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Obx(() => Text(
_recognizedText.value.isEmpty
? '识别结果将显示在这里...'
: _recognizedText.value,
style: const TextStyle(
fontSize: 18.0,
),
)),
),
),
),
),
const SizedBox(height: 20),
// 控制按钮
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() {
return ElevatedButton(
onPressed: _recognitionService.isInitialized.value && !_recognitionService.isListening.value
? _startRecognition
: null,
child: const Text('开始识别'),
);
}),
const SizedBox(width: 20),
Obx(() {
return ElevatedButton(
onPressed: _recognitionService.isListening.value
? _stopRecognition
: null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text('停止识别'),
);
}),
],
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Obx(() => ElevatedButton(
onPressed: _permissionsGranted.value && !_isRecognizing.value && !_isInitializing.value
? _startRecognition
: null,
child: const Text('开始识别'),
)),
Obx(() => ElevatedButton(
onPressed: _isRecognizing.value ? _stopRecognition : null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text('停止识别'),
)),
],
),
),
],
),

Loading…
Cancel
Save