Browse Source

优化唤醒速度

newdev_shunjiawei
fdp 1 year ago
parent
commit
3aef355477
  1. 42
      lib/modules/settings/views/settings_view.dart
  2. 22
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  3. 13
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  4. 1
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt
  5. 4
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  6. 46
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt
  7. 56
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

42
lib/modules/settings/views/settings_view.dart

@ -515,27 +515,27 @@ class SettingsView extends GetView<SettingsController> {
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
//BLE测试
// _buildSimpleNavigationSetting(
// title: 'opus解码测试',
// subtitle: '测试opus解码',
// icon: Icons.bluetooth_searching,
// iconBgColor: isDarkMode
// ? Colors.green[900]!.withOpacity(0.3)
// : Colors.green[100]!,
// iconColor:
// isDarkMode ? Colors.green[300]! : Colors.green[600]!,
// titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () {
// Get.toNamed(Routes.opusTest);
// },
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
_buildSimpleNavigationSetting(
title: 'opus解码测试',
subtitle: '测试opus解码',
icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!,
iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
Get.toNamed(Routes.opusTest);
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// BLE测试
// _buildSimpleNavigationSetting(

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

@ -130,6 +130,8 @@ object AgentService : CoroutineScope {
//是否播放音乐中
private var isplaymusic = false;
//是否保留最终结果
private var isKeepResult = true;
// 打断防抖相关
private var lastInterruptTime = 0L
@ -265,6 +267,7 @@ object AgentService : CoroutineScope {
*/
fun dispose() {
try {
BleService.closeCodec()
stopRecognition()
stopTts()
stopAiStream()
@ -474,6 +477,7 @@ object AgentService : CoroutineScope {
if (isActive && isRecognitionActive && !isTtsSpeaking && !isAiStreaming) {
Log.d(TAG, "空闲检测触发自动停止,模式: $currentRecognitionMode")
BleService.closeCodec()
stopRecognition()
sendEvent(
"auto_stop",
@ -516,13 +520,18 @@ object AgentService : CoroutineScope {
* @param mode 识别模式:normal, ble_wakeup, phone_call, push_to_talk
*/
fun startRecognition(isExternalActive: Boolean, mode: String = "normal"): Boolean {
if(isExternalActive){
isKeepResult = false
BleService.openEncoder()//不发指令。只是录制opus
}
if (!isInitialized) {
Log.d(TAG, "服务未初始化")
return false
}
Log.d("TAG", "开始语音识别startRecognition?")
stopRecognition()
BleService.openEncoder()
// if (isRecognitionActive&&audioSourceType==AzureAsrHelper.AudioSourceType.MICROPHONE){
// Log.d(TAG, "开始语音识别stopRecognition")
// // BleService.closeCodec()
@ -559,6 +568,9 @@ object AgentService : CoroutineScope {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
Log.d("TAG", "azureAsrHelper?.startContinuousRecognition")
if(isExternalActive){
isKeepResult = true
}
azureAsrHelper?.startContinuousRecognition(audioSourceType)
// 根据模式启动相应的空闲检测
@ -630,7 +642,7 @@ object AgentService : CoroutineScope {
}
override fun onResult(result: String, detectedLanguage: String) {
if (result.isNotEmpty()) {
if (result.isNotEmpty()&&isKeepResult==true) {//加入是否结束要输出最后的结果判读
sendEvent(
"recognition_result", mapOf(
"text" to result,
@ -709,11 +721,9 @@ object AgentService : CoroutineScope {
Log.d(TAG, "停止语音识别,当前模式: $currentRecognitionMode")
try {
_isRecognitionActive.set(false)
azureAsrHelper?.stopContinuousRecognition()
BleService.closeCodec()
_isRecognitionActive.set(false)
stopIdleCheck()
// 重置识别模式
currentRecognitionMode = "normal"
return
@ -721,7 +731,6 @@ object AgentService : CoroutineScope {
Log.e(TAG, "停止语音识别异常: ${e.message}")
_isRecognitionActive.set(false)
stopIdleCheck()
// 重置识别模式
currentRecognitionMode = "normal"
return
@ -1055,6 +1064,7 @@ object AgentService : CoroutineScope {
)
)
if (name == "exit_interaction") {
BleService.closeCodec()
stopRecognition()
}
} catch (e: Exception) {

13
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt

@ -14,6 +14,8 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import android.content.Intent
import android.os.Build
// 添加BleService的导入语句
import com.yunqiinnovation.ble_service.BleService
class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler,
AgentServiceListener {
@ -124,13 +126,14 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("START_ERROR", "启动对话失败: ${e.message}", null)
}
}
"recognizeCallback" -> {
val isStarted = AgentService.recognizeCallback()
result.success(isStarted)
}
"recognizeCallback" -> {
val isStarted = AgentService.recognizeCallback()
result.success(isStarted)
}
"stopConversation" -> {
try {
BleService.closeCodec()
AgentService.stopRecognition()
result.success(true)
} catch (e: Exception) {
@ -309,4 +312,4 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
eventChannel.setStreamHandler(null)
AgentService.removeListener(this)
}
}
}

1
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt

@ -77,6 +77,7 @@ Log.d(TAG, "手动启动语音识别: ")
Log.e(TAG, "语音代理未初始化")
return
}
BleService.closeCodec()
AgentService.stopRecognition()
return
}

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

@ -215,7 +215,7 @@ class AzureAsrHelper(private val context: Context) {
if (!audioStream.isInitialized()) {
audioStream.initAudioRecord()
}
audioStream.setupStream()
audioConfig = AudioConfig.fromStreamInput(audioStream.pushAudioStream)
} catch (e: Exception) {
@ -488,7 +488,7 @@ class AzureAsrHelper(private val context: Context) {
if (!audioStream.isInitialized()) {
audioStream.initAudioRecord()
}
audioStream.setupStream()
if (audioStream.recordfile == null) {
audioStream.recordfile = RecordFile()
}

46
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt

@ -88,8 +88,6 @@ class SimpleAudioReceiver(private val context: Context) {
* 初始化音频录制
*/
fun initAudioRecord() {
val format = getOptimalAudioFormat()
pushAudioStream = AudioInputStream.createPushStream(format)
isRunning.set(true)
Log.d(TAG, "initAudioRecord: ${isRunning}")
startWriteThread() // 再启动数据读取线程
@ -97,6 +95,17 @@ class SimpleAudioReceiver(private val context: Context) {
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
originalAudioMode = audioManager?.mode ?: AudioManager.MODE_NORMAL
isInitialized = true
}
/**
* 设置音频流
*/
fun setupStream() {
// 安全关闭并清理之前的流
pushAudioStream?.close()
pushAudioStream = null
val format = getOptimalAudioFormat()
pushAudioStream = AudioInputStream.createPushStream(format)
}
fun isInitialized(): Boolean = isInitialized
/**
@ -143,7 +152,7 @@ class SimpleAudioReceiver(private val context: Context) {
if (bytesToWrite < data.size) data.copyOf(bytesToWrite) else data
try {
Log.d(TAG, "写入数据大小: ${finalData.size},isContinuousRecognitionActive:${isContinuousRecognitionActive},isRecord${isRecord}")
//Log.d(TAG, "写入数据大小: ${finalData.size},isContinuousRecognitionActive:${isContinuousRecognitionActive},isRecord${isRecord}")
if(audioDataCallback!=null){
audioDataCallback?.onAudio(finalData)
}
@ -183,22 +192,36 @@ class SimpleAudioReceiver(private val context: Context) {
writeQueue.offer(buffer.copyOf())
}
/**
* 开始音频输入
* @param audioSourceType 音频源类型
* @param callback 音频数据回调,可为空
*/
fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallback?) {
/**
* 开始音频输入
* @param audioSourceType 音频源类型
* @param callback 音频数据回调,可为空
*/
fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallback?) {
try {
this.audioSourceType = audioSourceType
this.audioDataCallback = callback
writeQueue.clear()
this.audioDataCallback = callback
isWriting.set(true)
if(writeQueue.isNotEmpty()){
Log.d("tag", "存下来的数据: ${writeQueue?.size}")
}
when (audioSourceType) {
AudioSourceType.MICROPHONE -> runMicrophoneCapture()
AudioSourceType.EXTERNAL -> runExternalCapture()
}
} catch (e: Exception) {
Log.e(TAG, "启动音频录制失败: ${e.message}", e)
// 清理资源
pushAudioStream?.close()
pushAudioStream = null
isWriting.set(false)
throw e // 重新抛出异常让调用者处理
}
}
// ... existing code ...
/**
* 配置通话音频模式以优化回声消除
*/
@ -367,6 +390,7 @@ class SimpleAudioReceiver(private val context: Context) {
*/
fun stopMicrophoneCapture() {
try {
writeQueue.clear()
isWriting.set(false)
// 停止录音

56
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -178,7 +178,7 @@ object BleService {
*/
fun initialize(appContext: Context): Boolean {
if (isInitialized) return true
try {
this.context = appContext.applicationContext
// 初始化蓝牙管理器和适配器
@ -208,7 +208,7 @@ object BleService {
recordfile1 = RecordingFile(this.context)
recordfile1!!.fileName = "重新压缩"
isInitialized = true
Log.d(TAG, "BLE服务初始化成功")
startOpusStreamDecoding(false, 1, 16000, 40)
return true
} catch (e: Exception) {
Log.e(TAG, "BLE服务初始化失败: ${e.message}", e)
@ -838,22 +838,7 @@ startBytesStatistics()
recordfile?.saveAudioDataToWav(data)
// 将完整的帧加入队列,由专门的线程处理
audioDataQueue.offer(data)
// // 将接收到的数据添加到缓存中
// synchronized(audioDataBuffer) {
// audioDataBuffer.addAll(data.toList())
// // 当缓存达到阈值时,提取完整的帧进行解码
// while (audioDataBuffer.size >= AUDIO_BUFFER_SIZE) {
// // 提取一个完整的帧(1280字节)
// val frameData = ByteArray(AUDIO_BUFFER_SIZE)
// for (i in 0 until AUDIO_BUFFER_SIZE) {
// frameData[i] = audioDataBuffer.removeAt(0)
// }
// Log.d(TAG, "缓存达到阈值,提取 ${frameData.size} 字节帧进行解码")
// }
// }
} else {
// Log.d(TAG, "Opus解码流未启动,忽略音频数据")
}
@ -1243,7 +1228,7 @@ startBytesStatistics()
// Opus 解码相关
// ======================================================================================================
private fun startOpusStreamDecoding(
public fun startOpusStreamDecoding(
hasHeader: Boolean = false, // 通常BLE传输的Opus没有文件头
channel: Int = 1,
sampleRate: Int = 16000, // 确认设备端Opus编码采样率
@ -1634,7 +1619,12 @@ startBytesStatistics()
) // 或其他默认声道
)
}
fun closeCodec1(): Boolean {
recordfile1!!.closeFile()
recordfile!!.closeFile()
return true
}
/**
* 控制编解码 - 打开解码
*/
@ -1707,16 +1697,20 @@ startBytesStatistics()
*/
fun openEncoder(): Boolean {
Log.i(TAG, "打开编码0xB1")
startOpusStreamDecoding(false, 1, 16000, 40)
// 目前仅发送命令通知设备开始编码。
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(
BleConst.CODEC_CONTROL_ENCODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte() // 或设备期望的声道
)
)
recordfile1!!.closeFile()
recordfile1!!.creatingFiles()
recordfile!!.closeFile()
recordfile!!.creatingFiles()
// startOpusStreamDecoding(false, 1, 16000, 40)
// // 目前仅发送命令通知设备开始编码。
// return sendCommand(
// BleConst.CMD_CONTROL_CODEC.toByte(),
// byteArrayOf(
// BleConst.CODEC_CONTROL_ENCODE_ON.toByte(),
// BleConst.AUDIO_CHANNEL_RIGHT.toByte() // 或设备期望的声道
// )
// )
return true
}
@ -2102,7 +2096,7 @@ class RecordingFile(private val context: Context) {
}
/**
* 保存音频数据到 WAV 文件(异步)
* 保存音频数据到 opus 文件(异步)
*/
internal fun saveAudioDataToWav(buffer: ByteArray) {
if (fos == null || currentAudioFile == null) return

Loading…
Cancel
Save