Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
eeff649d4d
  1. 2
      lib/data/services/network/nw_api.dart
  2. 91
      lib/modules/agent/controllers/agent_controller.dart
  3. 55
      lib/modules/agent/views/agent_view.dart
  4. 1
      lib/modules/meeting/controllers/meeting_record_controller.dart
  5. 2
      lib/modules/translation/controllers/translation_controller.dart
  6. 80
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  7. 18
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  8. 2
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt
  9. 26
      local_plugins/agent_service/lib/agent_service.dart
  10. 701
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  11. 6
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  12. 9
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

2
lib/data/services/network/nw_api.dart

@ -1,5 +1,5 @@
class NWApi { class NWApi {
// static const baseApi = "https://www.deapsound.com"; // static const baseApi = "https://www.deapsound.com";
// static const baseApi = "https://api.deapsound.com"; //static const baseApi = "https://api.deapsound.com";
static const baseApi = "https://api.ideapsound.com"; static const baseApi = "https://api.ideapsound.com";
} }

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

@ -93,7 +93,7 @@ class AgentController extends GetxController {
final isPushToTalkActive = false.obs; // 是否正在按住说话 final isPushToTalkActive = false.obs; // 是否正在按住说话
// 按住说话模式状态 - 用于区分是否处于按住说话模式(即使当前没有按住) // 按住说话模式状态 - 用于区分是否处于按住说话模式(即使当前没有按住)
final isPushToTalkMode = false.obs; // 是否处于按住说话模式 //final isPushToTalkMode = false.obs; // 是否处于按住说话模式
// 蓝牙唤醒状态 // 蓝牙唤醒状态
final isBleWakeupActive = false.obs; // 是否正在进行蓝牙唤醒对话 final isBleWakeupActive = false.obs; // 是否正在进行蓝牙唤醒对话
@ -143,6 +143,7 @@ class AgentController extends GetxController {
_initMusicStatus(); //初始化音乐状态 _initMusicStatus(); //初始化音乐状态
_setupMediaSessionCallbacks(); //设置媒体会话回调 _setupMediaSessionCallbacks(); //设置媒体会话回调
_setupTextInputListener(); //设置文本输入监听器 _setupTextInputListener(); //设置文本输入监听器
startVoiceInput(); //开启语音输入
} }
// 初始化音乐播放状态 // 初始化音乐播放状态
@ -355,6 +356,7 @@ class AgentController extends GetxController {
case AgentServiceEventType.recognitionStarted: case AgentServiceEventType.recognitionStarted:
isListening.value = true; isListening.value = true;
isTextInputMode.value = false; isTextInputMode.value = false;
print("订阅代理服务事件识别开始");
// isTextInputMode.refresh(); // isTextInputMode.refresh();
// 只标记识别开始,不添加消息 // 只标记识别开始,不添加消息
Logger.i(TAG, '识别开始'); Logger.i(TAG, '识别开始');
@ -371,7 +373,7 @@ class AgentController extends GetxController {
} else { } else {
// 只有在非按住说话模式且非打电话模式下才自动切换到文字模式 // 只有在非按住说话模式且非打电话模式下才自动切换到文字模式
// 按住说话模式下应该保持在语音模式,等待下次按住 // 按住说话模式下应该保持在语音模式,等待下次按住
if (!isPushToTalkMode.value && !isPhoneCallMode.value) { if (!isPhoneCallMode.value) {
isTextInputMode.value = true; isTextInputMode.value = true;
} }
} }
@ -890,19 +892,18 @@ class AgentController extends GetxController {
// 切换到文本模式时,禁用按住说话模式并停止任何正在进行的语音输入 // 切换到文本模式时,禁用按住说话模式并停止任何正在进行的语音输入
if (isTextInputMode.value) { if (isTextInputMode.value) {
// 切换到文本模式 // 切换到文本模式
isPushToTalkMode.value = false; isTextInputMode.value = true;
stopVoiceInput(); // stopVoiceInput();
} else { } else {
// 切换到语音模式,启用按住说话模式 // 切换到语音模式,启用按住说话模式
isPushToTalkMode.value = true; isTextInputMode.value = false;
} }
} }
// 开始语音输入 // 开始语音输入
Future<void> startVoiceInput() async { Future<void> startVoiceInput() async {
if (isListening.value) return; if (isListening.value) return;
print("开始语音输入"); // 检查麦克风权限
// 检查麦克风权限
final hasPermission = await PermissionUtil.instance.requestPermission( final hasPermission = await PermissionUtil.instance.requestPermission(
// context: Get.context!, // context: Get.context!,
permissionType: Permission.microphone, permissionType: Permission.microphone,
@ -1129,13 +1130,14 @@ class AgentController extends GetxController {
// 保存当前输入模式 // 保存当前输入模式
void _saveCurrentInputMode() { void _saveCurrentInputMode() {
if (isPushToTalkMode.value) { if (!isTextInputMode.value) {
_previousInputMode = VoiceInputMode.pushToTalk; _previousInputMode = VoiceInputMode.pushToTalk;
} else if (isTextInputMode.value) {
_previousInputMode = VoiceInputMode.text;
} else { } else {
_previousInputMode = null; _previousInputMode = VoiceInputMode.text;
} }
// else {
// _previousInputMode = null;
// }
} }
// 恢复之前的输入模式 // 恢复之前的输入模式
@ -1143,16 +1145,16 @@ class AgentController extends GetxController {
switch (_previousInputMode) { switch (_previousInputMode) {
case VoiceInputMode.pushToTalk: case VoiceInputMode.pushToTalk:
isTextInputMode.value = false; isTextInputMode.value = false;
isPushToTalkMode.value = true; //isPushToTalkMode.value = true;
break; break;
case VoiceInputMode.text: case VoiceInputMode.text:
isTextInputMode.value = true; isTextInputMode.value = true;
isPushToTalkMode.value = false; // isPushToTalkMode.value = false;
break; break;
default: default:
// 默认恢复到文字模式 // 默认恢复到文字模式
isTextInputMode.value = true; isTextInputMode.value = true;
isPushToTalkMode.value = false; //isPushToTalkMode.value = false;
break; break;
} }
_previousInputMode = null; // 清除历史记录 _previousInputMode = null; // 清除历史记录
@ -1171,8 +1173,8 @@ class AgentController extends GetxController {
// 进入打电话模式 // 进入打电话模式
isPhoneCallMode.value = true; isPhoneCallMode.value = true;
// 如果当前是文本模式,切换到语音模式 // 如果当前是文本模式,切换到语音模式????
if (isTextInputMode.value) { if (isListening.value) {
// 检查麦克风权限 // 检查麦克风权限
final hasPermission = await PermissionUtil.instance.requestPermission( final hasPermission = await PermissionUtil.instance.requestPermission(
permissionType: Permission.microphone, permissionType: Permission.microphone,
@ -1193,8 +1195,10 @@ class AgentController extends GetxController {
// 切换到语音模式并开始语音输入 // 切换到语音模式并开始语音输入
isTextInputMode.value = false; isTextInputMode.value = false;
// 打电话模式不使用按住说话模式,而是持续语音输入 // 打电话模式不使用按住说话模式,而是持续语音输入
isPushToTalkMode.value = false; //isPushToTalkMode.value = false;
await startVoiceInput(); // 开始语音识别
print("开始打电话模式,开始语音输入");
await AgentService.audioContinue();
} }
} }
@ -1206,8 +1210,9 @@ class AgentController extends GetxController {
isPhoneCallMode.value = false; isPhoneCallMode.value = false;
// 停止语音输入 // 停止语音输入
await stopVoiceInput(); //await stopVoiceInput();
// 开始语音识别
await AgentService.audioPause();
// 恢复之前的输入模式,而不是强制切换到文字模式 // 恢复之前的输入模式,而不是强制切换到文字模式
_restorePreviousInputMode(); _restorePreviousInputMode();
} }
@ -1240,23 +1245,23 @@ class AgentController extends GetxController {
// 验证语音输入状态一致性 // 验证语音输入状态一致性
void _validateVoiceInputStates() { void _validateVoiceInputStates() {
// 检查状态冲突 // 检查状态冲突
if (isPhoneCallMode.value && isPushToTalkMode.value) { // if (isPhoneCallMode.value && isPushToTalkMode.value) {
Logger.w(TAG, '状态冲突:同时启用打电话和按住说话模式,优先保持打电话模式'); // Logger.w(TAG, '状态冲突:同时启用打电话和按住说话模式,优先保持打电话模式');
isPushToTalkMode.value = false; // isPushToTalkMode.value = false;
} // }
// 检查文字模式与语音模式的一致性 // // 检查文字模式与语音模式的一致性
if (isTextInputMode.value && // if (isTextInputMode.value &&
(isPushToTalkMode.value || isPhoneCallMode.value)) { // (isPushToTalkMode.value || isPhoneCallMode.value)) {
Logger.w(TAG, '状态冲突:文字模式与语音模式同时启用,优先保持语音模式'); // Logger.w(TAG, '状态冲突:文字模式与语音模式同时启用,优先保持语音模式');
isTextInputMode.value = false; // isTextInputMode.value = false;
} // }
// 检查按住说话状态的合理性 // // 检查按住说话状态的合理性
if (isPushToTalkActive.value && !isPushToTalkMode.value) { // if (isPushToTalkActive.value && !isPushToTalkMode.value) {
Logger.w(TAG, '状态冲突:按住说话激活但未启用按住说话模式'); // Logger.w(TAG, '状态冲突:按住说话激活但未启用按住说话模式');
isPushToTalkActive.value = false; // isPushToTalkActive.value = false;
} // }
} }
// 安全的状态切换方法 // 安全的状态切换方法
@ -1266,21 +1271,21 @@ class AgentController extends GetxController {
switch (targetMode) { switch (targetMode) {
case VoiceInputMode.text: case VoiceInputMode.text:
isTextInputMode.value = true; isTextInputMode.value = true;
isPushToTalkMode.value = false; // isPushToTalkMode.value = false;
isPhoneCallMode.value = false; isPhoneCallMode.value = false;
isPushToTalkActive.value = false; isPushToTalkActive.value = false;
break; break;
case VoiceInputMode.pushToTalk: case VoiceInputMode.pushToTalk:
isTextInputMode.value = false; isTextInputMode.value = false;
isPushToTalkMode.value = true; // isPushToTalkMode.value = true;
isPhoneCallMode.value = false; isPhoneCallMode.value = false;
isPushToTalkActive.value = false; isPushToTalkActive.value = false;
break; break;
case VoiceInputMode.phoneCall: case VoiceInputMode.phoneCall:
isTextInputMode.value = false; isTextInputMode.value = false;
isPushToTalkMode.value = false; // isPushToTalkMode.value = false;
isPhoneCallMode.value = true; isPhoneCallMode.value = true;
isPushToTalkActive.value = false; isPushToTalkActive.value = false;
break; break;
@ -1295,7 +1300,7 @@ class AgentController extends GetxController {
Logger.i(TAG, '🎤 开始按住说话'); Logger.i(TAG, '🎤 开始按住说话');
// 只有在语音模式且按住说话模式下才能按住说话 // 只有在语音模式且按住说话模式下才能按住说话
if (isTextInputMode.value || !isPushToTalkMode.value) return; if (isTextInputMode.value) return;
// 如果已经在按住说话,不重复开始 // 如果已经在按住说话,不重复开始
if (isPushToTalkActive.value) return; if (isPushToTalkActive.value) return;
@ -1303,8 +1308,10 @@ class AgentController extends GetxController {
// 设置按住说话状态 // 设置按住说话状态
isPushToTalkActive.value = true; isPushToTalkActive.value = true;
print("开始按住说话,状态已设置为true");
// 开始语音识别 // 开始语音识别
await startVoiceInput(); await AgentService.audioContinue();
// await startVoiceInput();
} }
// 结束按住说话 // 结束按住说话
@ -1316,9 +1323,9 @@ class AgentController extends GetxController {
// 清除按住说话状态(但保持按住说话模式) // 清除按住说话状态(但保持按住说话模式)
isPushToTalkActive.value = false; isPushToTalkActive.value = false;
await AgentService.audioPause();
// 停止语音识别 // 停止语音识别
await stopVoiceInput(); // await stopVoiceInput();
} }
// 获取音乐播放状态 // 获取音乐播放状态

55
lib/modules/agent/views/agent_view.dart

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -374,7 +375,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
final isTextMode = final isTextMode =
controller.isTextInputMode.value; controller.isTextInputMode.value;
if (isTextMode) { if (controller.isTextInputMode.value) {
// 文本输入模式 - 限制最大高度并只向上扩展 // 文本输入模式 - 限制最大高度并只向上扩展
return Container( return Container(
constraints: const BoxConstraints( constraints: const BoxConstraints(
@ -466,16 +467,44 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
), ),
); );
} else { } else {
Timer? _holdTimer; // 增加延迟触发的计时器
// 语音输入模式 - 按住说话模式 // 语音输入模式 - 按住说话模式
return Listener( return Listener(
onPointerDown: (_) { onPointerDown: (_) {
// 开始按住说话 // 设置300ms延迟防止误触
controller.startPushToTalk(); _holdTimer = Timer(
const Duration(milliseconds: 200),
() {
controller.startPushToTalk();
// if (activeSpeaker == 0) {
// if (speakerId == 1) {
// controller
// .restoreOriginalAudioState();
// } else {
// controller
// .disableBluetoothAudio();
// }
// controller
// .setActiveSpeaker(speakerId);
// }
});
}, },
onPointerUp: (_) { onPointerUp: (_) {
// 结束按住说话 _holdTimer?.cancel();
controller.endPushToTalk(); controller.endPushToTalk();
}, },
onPointerCancel: (_) {
_holdTimer?.cancel();
controller.endPushToTalk();
},
// onLongPressStart: (_) {
// // 开始按住说话
// controller.startPushToTalk();
// },
// onLongPressEnd: (_) {
// // 结束按住说话
// controller.endPushToTalk();
// },
// onLongPressCancel: () { // onLongPressCancel: () {
// // 取消按住说话 // // 取消按住说话
// controller.endPushToTalk(); // controller.endPushToTalk();
@ -517,7 +546,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
// 语音波形图 - 只在按住时显示 // 语音波形图 - 只在按住时显示
if (isPushToTalkActive) ...[ if (isPushToTalkActive) ...[
const AgentSoundBarsWidget(), const AgentSoundBarsWidget(),
//const Spacer(), const Spacer(),
], ],
// 状态文字和图标 // 状态文字和图标
@ -526,7 +555,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
mainAxisAlignment: mainAxisAlignment:
isPushToTalkActive isPushToTalkActive
? MainAxisAlignment ? MainAxisAlignment
.center .end
: MainAxisAlignment : MainAxisAlignment
.center, .center,
children: [ children: [
@ -1020,13 +1049,13 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
Obx(() { Obx(() {
final isActive = controller.isListening.value; final isActive = controller.isListening.value;
return GestureDetector( return GestureDetector(
onTap: () { // onTap: () {
if (isActive) { // if (isActive) {
controller.stopVoiceInput(); // controller.stopVoiceInput();
} else { // } else {
controller.startVoiceInput(); // controller.startVoiceInput();
} // }
}, // },
child: Container( child: Container(
width: 80, width: 80,
height: 80, height: 80,

1
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -138,7 +138,6 @@ class MeetingRecordController extends GetxController
_bleManager.closeCodec(); _bleManager.closeCodec();
} }
_asrService.stopRecord(true); _asrService.stopRecord(true);
_asrService.setPause();
_asrService.stopContinuousRecognition(); _asrService.stopContinuousRecognition();
isRecording.value = false; isRecording.value = false;
} }

2
lib/modules/translation/controllers/translation_controller.dart

@ -430,7 +430,7 @@ class TranslationController extends GetxController {
try { try {
await _asrService.stopContinuousRecognition(); await _asrService.stopContinuousRecognition();
_asrService.setPause();
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
_recognitionSubscription = null; _recognitionSubscription = null;
isRecognizing.value = false; isRecognizing.value = false;

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

@ -58,7 +58,7 @@ object AgentService : CoroutineScope {
private val listeners = Collections.synchronizedSet(mutableSetOf<AgentServiceListener>()) private val listeners = Collections.synchronizedSet(mutableSetOf<AgentServiceListener>())
// Azure服务 // Azure服务
private var azureAsrHelper: AzureAsrHelper? = null var azureAsrHelper: AzureAsrHelper? = null
// 使用BytedanceTTS作为唯一的TTS实现 // 使用BytedanceTTS作为唯一的TTS实现
private var ttsService: AzureTtsHelper? = null private var ttsService: AzureTtsHelper? = null
@ -111,6 +111,7 @@ object AgentService : CoroutineScope {
private var lastInterruptTime = 0L private var lastInterruptTime = 0L
private val INTERRUPT_DEBOUNCE_MS = 300L private val INTERRUPT_DEBOUNCE_MS = 300L
private var audioSourceType= AzureAsrHelper.AudioSourceType.MICROPHONE
// 语气词过滤列表(中英文) // 语气词过滤列表(中英文)
private val FILLER_WORDS = setOf( private val FILLER_WORDS = setOf(
// 中文语气词 // 中文语气词
@ -351,15 +352,16 @@ object AgentService : CoroutineScope {
*/ */
private fun startIdleCheck() { private fun startIdleCheck() {
stopIdleCheck() // 先停止现有的检查 stopIdleCheck() // 先停止现有的检查
Log.d(TAG, "启动空闲检测: ${isRecognitionActive}")
if (!isRecognitionActive) return if (!isRecognitionActive) return
idleCheckJob = launch { idleCheckJob = launch {
try { try {
delay(maxIdleSeconds * 1000L) delay(maxIdleSeconds * 1000L)
if (isActive && isRecognitionActive && !hasSpeechDetected && !isTtsSpeaking && !isAiStreaming) { if (isActive && isRecognitionActive && !hasSpeechDetected && !isTtsSpeaking && !isAiStreaming) {
stopRecognition() Log.d(TAG, "启动空闲检测: stopRecognition")
//stopRecognition()
sendEvent("auto_stop", mapOf("reason" to "idle_timeout", "seconds" to maxIdleSeconds)) sendEvent("auto_stop", mapOf("reason" to "idle_timeout", "seconds" to maxIdleSeconds))
} }
} catch (e: CancellationException) { } catch (e: CancellationException) {
@ -393,10 +395,17 @@ object AgentService : CoroutineScope {
*/ */
fun startRecognition(isExternalActive: Boolean): Boolean { fun startRecognition(isExternalActive: Boolean): Boolean {
if (!isInitialized) { if (!isInitialized) {
Log.e(TAG, "服务未初始化") Log.d(TAG, "服务未初始化")
return false return false
} }
Log.d(TAG, "麦克风音频未暂停,无法设置${azureAsrHelper!!.isPaused},$isExternalActive")
if(!(azureAsrHelper!!.isPaused)&&isExternalActive)
{
Log.d(TAG, "麦克风音频未暂停,无法设置")
BleService.closeCodec()
return false
}
Log.d(TAG, "开始语音识别")
if (isRecognitionActive) return true if (isRecognitionActive) return true
// 检查录音权限 // 检查录音权限
@ -410,7 +419,7 @@ object AgentService : CoroutineScope {
_hasSpeechDetected.set(false) _hasSpeechDetected.set(false)
try { try {
val audioSourceType = if (isExternalActive) { audioSourceType = if (isExternalActive) {
AzureAsrHelper.AudioSourceType.EXTERNAL AzureAsrHelper.AudioSourceType.EXTERNAL
} else { } else {
AzureAsrHelper.AudioSourceType.MICROPHONE AzureAsrHelper.AudioSourceType.MICROPHONE
@ -480,6 +489,7 @@ object AgentService : CoroutineScope {
override fun onSessionStarted() { override fun onSessionStarted() {
sendEvent("recognition_started", mapOf("status" to "started")) sendEvent("recognition_started", mapOf("status" to "started"))
startIdleCheck() startIdleCheck()
Log.d(TAG, "onSessionStarted")
audioPlayer?.playAudio(R.raw.start) audioPlayer?.playAudio(R.raw.start)
} }
@ -489,12 +499,14 @@ object AgentService : CoroutineScope {
_isRecognitionActive.set(false) _isRecognitionActive.set(false)
stopIdleCheck() stopIdleCheck()
Log.d(TAG, "onSessionStopped")
audioPlayer?.playAudio(R.raw.stop) audioPlayer?.playAudio(R.raw.stop)
} }
override fun onCanceled(reason: String, errorDetails: String) { override fun onCanceled(reason: String, errorDetails: String) {
_isRecognitionActive.set(false) _isRecognitionActive.set(false)
stopIdleCheck() stopIdleCheck()
Log.d(TAG, "recognition_canceled")
BleService.closeCodec() BleService.closeCodec()
sendEvent("recognition_canceled", mapOf( sendEvent("recognition_canceled", mapOf(
"reason" to reason, "reason" to reason,
@ -505,6 +517,7 @@ object AgentService : CoroutineScope {
override fun onError(error: String) { override fun onError(error: String) {
_isRecognitionActive.set(false) _isRecognitionActive.set(false)
stopIdleCheck() stopIdleCheck()
Log.d(TAG, "error")
BleService.closeCodec() BleService.closeCodec()
Log.e(TAG, "语音识别错误: $error") Log.e(TAG, "语音识别错误: $error")
sendEvent("error", mapOf( sendEvent("error", mapOf(
@ -536,6 +549,7 @@ object AgentService : CoroutineScope {
try { try {
azureAsrHelper?.stopContinuousRecognition() azureAsrHelper?.stopContinuousRecognition()
Log.d(TAG, "停止语音识别")
BleService.closeCodec() BleService.closeCodec()
_isRecognitionActive.set(false) _isRecognitionActive.set(false)
stopIdleCheck() stopIdleCheck()
@ -545,7 +559,55 @@ object AgentService : CoroutineScope {
stopIdleCheck() stopIdleCheck()
} }
} }
/**
* 继续音频输入
*/
fun audioContinue() {
try {
if(audioSourceType== AzureAsrHelper.AudioSourceType.EXTERNAL)
{
Log.d(TAG, "当前是什么输入:$audioSourceType")
BleService.closeCodec()
stopRecognition()
startRecognition(false)
}
// if (!isRecognitionActive) {
// }
azureAsrHelper?.audioContinue()
//BleService.closeCodec()
//_isRecognitionActive.set(false)
//stopIdleCheck()
} catch (e: Exception) {
Log.e(TAG, "暂停音频输入异常: ${e.message}")
// _isRecognitionActive.set(false)
//stopIdleCheck()
}
}
/**
* 暂停音频输入
*/
fun audioPause() {
if (!isRecognitionActive) return
try {
azureAsrHelper?.audioPause()
Log.d(TAG, "暂停音频输入")
BleService.closeCodec()
//_isRecognitionActive.set(false)
//stopIdleCheck()
} catch (e: Exception) {
Log.e(TAG, "暂停音频输入异常: ${e.message}")
// _isRecognitionActive.set(false)
//stopIdleCheck()
}
}
/** /**
* 打断当前响应 * 打断当前响应
* 停止TTS播放和AI流输出 * 停止TTS播放和AI流输出
@ -676,6 +738,7 @@ object AgentService : CoroutineScope {
hasImage: Boolean = false, hasImage: Boolean = false,
imagePath: String? = null imagePath: String? = null
) { ) {
Log.d(TAG, "内部方法:通用的ChatAPI处理逻辑")
// 如果有正在进行的AI流式输出,先停止它 // 如果有正在进行的AI流式输出,先停止它
stopAiStream() stopAiStream()
stopTts() stopTts()
@ -685,6 +748,7 @@ object AgentService : CoroutineScope {
currsessionId = UUID.randomUUID().toString() currsessionId = UUID.randomUUID().toString()
// 设置状态为正在流式输出 // 设置状态为正在流式输出
_isAiStreaming.set(true) _isAiStreaming.set(true)
Log.d(TAG, "processWithChatApiInternal")
audioPlayer?.playAudio(R.raw.await, true,0.3f) audioPlayer?.playAudio(R.raw.await, true,0.3f)
// 使用历史记录作为上下文发送到OpenAI // 使用历史记录作为上下文发送到OpenAI
val responseBuilder = StringBuilder() val responseBuilder = StringBuilder()
@ -1190,6 +1254,7 @@ object AgentService : CoroutineScope {
fun playAudio(resId: Int, isLooping: Boolean = false, volume: Float = 1.0f) { fun playAudio(resId: Int, isLooping: Boolean = false, volume: Float = 1.0f) {
synchronized(playbackLock) { synchronized(playbackLock) {
try { try {
Log.d(TAG, "playAudio${isLooping}")
// 强制停止并释放之前的资源 // 强制停止并释放之前的资源
forceStop() forceStop()
@ -1233,6 +1298,7 @@ object AgentService : CoroutineScope {
fun stopAudio() { fun stopAudio() {
synchronized(playbackLock) { synchronized(playbackLock) {
try { try {
Log.d(TAG, "stopAudio")
mediaPlayer?.apply { mediaPlayer?.apply {
if (isMediaPlayerPlaying()) { if (isMediaPlayerPlaying()) {
stop() stop()

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

@ -103,6 +103,24 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
Log.e(TAG, "停止对话失败", e) Log.e(TAG, "停止对话失败", e)
result.error("STOP_ERROR", "停止对话失败: ${e.message}", null) result.error("STOP_ERROR", "停止对话失败: ${e.message}", null)
} }
}
"audioContinue" -> {
try {
AgentService.audioContinue()
result.success(true)
} catch (e: Exception) {
Log.e(TAG, "audioContinue失败", e)
result.error("STOP_ERROR", "audioContinue失败: ${e.message}", null)
}
}
"audioPause" -> {
try {
AgentService.audioPause()
result.success(true)
} catch (e: Exception) {
Log.e(TAG, "audioContinue失败", e)
result.error("STOP_ERROR", "audioContinue失败: ${e.message}", null)
}
} }
"processTextInput" -> { "processTextInput" -> {
val text = call.argument<String>("text") val text = call.argument<String>("text")

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

@ -142,7 +142,7 @@ object BleAgent : BleService.Callback, AgentServiceListener {
AgentService.interruptCurrentResponse() AgentService.interruptCurrentResponse()
// AgentService.speakText("我在!") // AgentService.speakText("我在!")
// 启动语音识别 // 启动语音识别
if (AgentService.isRecognitionActive) { if (AgentService.isRecognitionActive&&AgentService.azureAsrHelper!!.isPaused) {
// 如果已经在识别,则重新启动 // 如果已经在识别,则重新启动
stopListening() stopListening()
} }

26
local_plugins/agent_service/lib/agent_service.dart

@ -261,6 +261,32 @@ class AgentService {
} }
} }
/// 停止对话
///
/// 停止语音识别
/// 返回是否成功停止对话
static Future<bool> audioContinue() async {
try {
final bool result = await _channel.invokeMethod('audioContinue');
return result;
} on PlatformException catch (e) {
throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details);
}
}
/// 停止对话
///
/// 停止语音识别
/// 返回是否成功停止对话
static Future<bool> audioPause() async {
try {
final bool result = await _channel.invokeMethod('audioPause');
return result;
} on PlatformException catch (e) {
throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details);
}
}
/// 处理文本输入 /// 处理文本输入
/// ///
/// [text] 文本内容 /// [text] 文本内容

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

@ -34,9 +34,6 @@ class AzureAsrHelper(private val context: Context) {
private val tag = "AzureAsrHelper" private val tag = "AzureAsrHelper"
// 核心组件 // 核心组件
private var echoCanceler: AcousticEchoCanceler? = null
private var noiseSuppressor: NoiseSuppressor? = null
private var automaticGainControl: AutomaticGainControl? = null
private var speechConfig: SpeechConfig? = null private var speechConfig: SpeechConfig? = null
private var recognizer: SpeechRecognizer? = null private var recognizer: SpeechRecognizer? = null
private var audioConfig: AudioConfig? = null private var audioConfig: AudioConfig? = null
@ -55,7 +52,7 @@ class AzureAsrHelper(private val context: Context) {
var audioSourceType = AudioSourceType.MICROPHONE var audioSourceType = AudioSourceType.MICROPHONE
// 音频处理 // 音频处理
// private var microphoneStream: MicrophoneStream? = null private var audioStream: AudioStream? = null
private var externalAudioStream: ExternalAudioPullStream? = null private var externalAudioStream: ExternalAudioPullStream? = null
// 音频模式管理 // 音频模式管理
@ -64,11 +61,7 @@ class AzureAsrHelper(private val context: Context) {
// 录音文件处理 // 录音文件处理
var recordfile: RecordFile? = null var recordfile: RecordFile? = null
var isPaused = true
private var isPaused = false
private var audioRecord: AudioRecord? = null
private var captureThread: Thread? = null
private var audioStream: PushAudioInputStream? = null
/** /**
* 音频来源类型 * 音频来源类型
@ -129,9 +122,7 @@ class AzureAsrHelper(private val context: Context) {
// 释放之前的资源 // 释放之前的资源
dispose() dispose()
// 1. 创建可控制的音频流
val format = getOptimalAudioFormat()
audioStream = AudioInputStream.createPushStream(format)
// 初始化音频管理器 // 初始化音频管理器
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
originalAudioMode = audioManager?.mode ?: AudioManager.MODE_NORMAL originalAudioMode = audioManager?.mode ?: AudioManager.MODE_NORMAL
@ -197,6 +188,7 @@ class AzureAsrHelper(private val context: Context) {
*/ */
private fun setupRecognizer(): Boolean { private fun setupRecognizer(): Boolean {
try { try {
// 清理旧的识别器 // 清理旧的识别器
recognizer?.close() recognizer?.close()
recognizer = null recognizer = null
@ -211,7 +203,11 @@ class AzureAsrHelper(private val context: Context) {
AudioSourceType.EXTERNAL -> { AudioSourceType.EXTERNAL -> {
// 改用拉流方式处理外部音频 // 改用拉流方式处理外部音频
Log.d(tag, "改用拉流方式处理外部音频")
setupExternalAudioStream() setupExternalAudioStream()
} }
} }
@ -237,13 +233,25 @@ class AzureAsrHelper(private val context: Context) {
*/ */
private fun setupMicrophoneStream() { private fun setupMicrophoneStream() {
try { try {
Log.d(tag, "设置麦克风流 - 使用拉流方式: }")
restoreOriginalAudioState()
if (audioStream == null) {
// 创建外部音频拉流对象
audioStream = AudioStream()
audioStream!!.initAudioRecord()
}
// else
// {
// audioStream.stopCaptureThread()
// audioStream.startCaptureThread()
// }
// 创建麦克风流 // 创建麦克风流
// microphoneStream = MicrophoneStream() // microphoneStream = MicrophoneStream()
// 创建音频配置 - 正确使用fromStreamInput方法,只传入回调 // 创建音频配置 - 正确使用fromStreamInput方法,只传入回调
// audioConfig = AudioConfig.fromStreamInput(microphoneStream) // audioConfig = AudioConfig.fromStreamInput(microphoneStream)
audioConfig = AudioConfig.fromStreamInput(audioStream) audioConfig = AudioConfig.fromStreamInput(audioStream!!.pushAudioStream)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "设置麦克风流失败: ${e.message}") Log.e(tag, "设置麦克风流失败: ${e.message}")
@ -278,6 +286,8 @@ class AzureAsrHelper(private val context: Context) {
*/ */
private fun setupExternalAudioStream() { private fun setupExternalAudioStream() {
try { try {
Log.d(tag, "设置外部音频流 - 使用拉流方式: }")
//disableBluetoothAudio()
// 创建外部音频拉流对象 // 创建外部音频拉流对象
externalAudioStream = ExternalAudioPullStream() externalAudioStream = ExternalAudioPullStream()
@ -299,7 +309,6 @@ class AzureAsrHelper(private val context: Context) {
if (audioSourceType != AudioSourceType.EXTERNAL) { if (audioSourceType != AudioSourceType.EXTERNAL) {
return return
} }
// 使用拉流模式,将数据推入队列 // 使用拉流模式,将数据推入队列
externalAudioStream?.pushAudio(data) externalAudioStream?.pushAudio(data)
@ -379,8 +388,7 @@ class AzureAsrHelper(private val context: Context) {
} }
try { try {
// 5. 初始化麦克风
initAudioRecord()
// 设置各种事件监听 // 设置各种事件监听
setupEventListeners(callback) setupEventListeners(callback)
@ -389,9 +397,7 @@ class AzureAsrHelper(private val context: Context) {
// 开始连续识别 // 开始连续识别
recognizer?.startContinuousRecognitionAsync() recognizer?.startContinuousRecognitionAsync()
// 开始录音 audioStream?.startCaptureThread()
audioRecord?.startRecording()
startCaptureThread() // 再启动数据读取线程
isContinuousRecognitionActive = true isContinuousRecognitionActive = true
@ -442,6 +448,7 @@ class AzureAsrHelper(private val context: Context) {
recognizer?.sessionStarted?.addEventListener( recognizer?.sessionStarted?.addEventListener(
EventHandler<SessionEventArgs> { _, _ -> EventHandler<SessionEventArgs> { _, _ ->
// 直接在当前线程调用回调 // 直接在当前线程调用回调
Log.d(tag, "会话开始事件")
callback.onSessionStarted() callback.onSessionStarted()
} }
) )
@ -450,6 +457,7 @@ class AzureAsrHelper(private val context: Context) {
recognizer?.sessionStopped?.addEventListener( recognizer?.sessionStopped?.addEventListener(
EventHandler<SessionEventArgs> { _, _ -> EventHandler<SessionEventArgs> { _, _ ->
// 直接在当前线程调用回调 // 直接在当前线程调用回调
Log.d(tag, "会话结束事件")
callback.onSessionStopped() callback.onSessionStopped()
isContinuousRecognitionActive = false isContinuousRecognitionActive = false
stopAudioProcessing() stopAudioProcessing()
@ -497,8 +505,6 @@ class AzureAsrHelper(private val context: Context) {
// 停止音频处理 // 停止音频处理
stopAudioProcessing() stopAudioProcessing()
// 停止录音
stopMicrophoneCapture()
// 会话结束事件会设置isContinuousRecognitionActive = false // 会话结束事件会设置isContinuousRecognitionActive = false
return true return true
} catch (e: Exception) { } catch (e: Exception) {
@ -508,8 +514,6 @@ class AzureAsrHelper(private val context: Context) {
// 停止音频处理 // 停止音频处理
stopAudioProcessing() stopAudioProcessing()
// 停止录音
stopMicrophoneCapture()
// 尝试强制关闭识别器 // 尝试强制关闭识别器
try { try {
recognizer?.close() recognizer?.close()
@ -538,7 +542,6 @@ class AzureAsrHelper(private val context: Context) {
recognizer?.stopContinuousRecognitionAsync() recognizer?.stopContinuousRecognitionAsync()
isContinuousRecognitionActive = false isContinuousRecognitionActive = false
} }
releaseAudioResources()
// 停止音频处理 // 停止音频处理
stopAudioProcessing() stopAudioProcessing()
@ -552,9 +555,6 @@ class AzureAsrHelper(private val context: Context) {
speechConfig?.close() speechConfig?.close()
speechConfig = null speechConfig = null
// 关闭音频流(根据实际实现可能需要)
audioStream?.close()
audioStream = null
// 释放音频配置 // 释放音频配置
audioConfig?.close() audioConfig?.close()
@ -601,16 +601,19 @@ class AzureAsrHelper(private val context: Context) {
* 停止音频处理 * 停止音频处理
*/ */
private fun stopAudioProcessing() { private fun stopAudioProcessing() {
// microphoneStream?.let { audioStream?.let {
// try { try {
// it.close() it.stopMicrophoneCapture()
// microphoneStream = null it.releaseAudioResources()
// } catch (e: Exception) {
// Log.e(tag, "关闭麦克风流失败: ${e.message}") it.pushAudioStream?.close()
// e.printStackTrace()
// }
// }
audioStream = null
} catch (e: Exception) {
Log.e(tag, "关闭麦克风流失败: ${e.message}")
e.printStackTrace()
}
} // 关闭音频流(根据实际实现可能需要)
externalAudioStream?.let { externalAudioStream?.let {
try { try {
it.close() it.close()
@ -622,70 +625,22 @@ class AzureAsrHelper(private val context: Context) {
} }
} }
/**
* 开启录音
*/
fun enableRecord(filePath: String) {
Log.i(tag, "开启录音:")
recordfile!!.closeFile(true)
recordfile!!.creatingFiles(filePath)
}
/**
* 移动文件到新路径
*/
fun moveFile(sourcePath: String, destPath: String): Boolean {
Log.i(tag, "移动文件到新路径:")
recordfile!!.moveFile(sourcePath, destPath)
return true
}
/**
* 重命名指定路径的音频文件
*/
fun renameFile(filePath: String, newName: String): Boolean {
Log.i(tag, "重命名指定路径的音频文件:")
recordfile!!.renameFile(filePath, newName)
return true
}
/**
* 停止录音
*/
fun pauseRecord() {
Log.i(tag, "停止连续录音:")
recordfile!!.isPause = true
}
/** /*
* 关闭录音 暂停音频输入
*/ */
fun stopRecord(isSave: Boolean) { fun audioPause() {
Log.i(tag, "关闭录音:")
recordfile!!.isPause = false
recordfile!!.closeFile(isSave)
}
fun stopAudioPause() {
Log.d(tag, "关闭麦克风stopAudioPause: ") Log.d(tag, "关闭麦克风stopAudioPause: ")
// 清理旧的识别器 // 清理旧的识别器
isPaused = true isPaused = true
} }
fun setAudiocontinue() { /*
继续音频输入
*/
fun audioContinue() {
Log.d(tag, "继续麦克风stopAudioPause: ") Log.d(tag, "继续麦克风stopAudioPause: ")
// 开始连续识别 // 开始连续识别
@ -727,403 +682,251 @@ class AzureAsrHelper(private val context: Context) {
} }
} }
//录音音频文件
/** /**
* 开始麦克风捕获 * 开启录音
*/ */
private fun initAudioRecord() { fun enableRecord(filePath: String) {
// 获取已确定的采样率 Log.i(tag, "开启录音:")
val format = getOptimalAudioFormat()
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100)
val channelConfig = AudioFormat.CHANNEL_IN_MONO // 单声道输入
val audioFormat = AudioFormat.ENCODING_PCM_16BIT // 16位PCM格式
// 查找设备支持的最佳采样率
val sampleRate = supportedSampleRates.firstOrNull { rate ->
val bufferSize = AudioRecord.getMinBufferSize(
rate,
channelConfig,
audioFormat
)
bufferSize > 0 // 返回正值表示支持
} ?: 16000 // 默认回退值
// 计算最小缓冲区大小(避免出现音频欠载)
val minBufferSize = AudioRecord.getMinBufferSize(
sampleRate,
channelConfig,
audioFormat
)
// 创建录音对象
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
Log.d("AzureASR", "android.os.Build.VERSION.SDK_INT")
val format = AudioFormat.Builder()
.setSampleRate(sampleRate)
.setEncoding(audioFormat)
.setChannelMask(channelConfig)
.build()
audioRecord = AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
.setAudioFormat(format)
.setBufferSizeInBytes(minBufferSize * 2)
.build()
} else {
// 创建 AudioRecord 对象
audioRecord = AudioRecord(
MediaRecorder.AudioSource.VOICE_RECOGNITION, // 语音识别专用音源(自带降噪/回声消除)
sampleRate,
channelConfig,
audioFormat,
minBufferSize * 2 // 使用两倍缓冲区防止溢出
)
}
recordfile!!.closeFile(true)
// 检查初始化状态
if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) {
throw IllegalStateException("AudioRecord初始化失败")
}
recordfile!!.creatingFiles(filePath)
} }
// 3. 单独封装线程启动逻辑 /**
private fun startCaptureThread() { * 移动文件到新路径
captureThread = Thread { */
// 根据实际采样率计算缓冲区大小 fun moveFile(sourcePath: String, destPath: String): Boolean {
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100) Log.i(tag, "移动文件到新路径:")
var bufferSize = 0
// 查找设备支持的最佳采样率
val sampleRate = supportedSampleRates.firstOrNull { rate ->
bufferSize = AudioRecord.getMinBufferSize(
rate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
bufferSize > 0 // 返回正值表示支持
} ?: 16000 // 默认回退值
val buffer = ByteArray(bufferSize) recordfile!!.moveFile(sourcePath, destPath)
return true
}
while (!Thread.interrupted() && audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
/**
* 重命名指定路径的音频文件
*/
fun renameFile(filePath: String, newName: String): Boolean {
Log.i(tag, "重命名指定路径的音频文件:")
if (!isPaused) {
// 读取音频
val bytesRead = audioRecord?.read(buffer, 0, buffer.size) ?: 0
if (bytesRead > 0) {
// 写入Azure流
audioStream?.write(buffer)
// 保存录音 recordfile!!.renameFile(filePath, newName)
recordfile?.saveAudioDataToWav(buffer) return true
}
}
}
}.apply { start() }
} }
/** /**
* 停止麦克风捕获并释放所有相关资源 * 停止录音
*/ */
private fun stopMicrophoneCapture() { fun pauseRecord() {
try { Log.i(tag, "停止连续录音:")
// 中断并等待捕获线程结束
captureThread?.interrupt()
captureThread?.join(300) // 最多等待300ms
// 停止录音并释放AudioRecord资源 recordfile!!.isPause = true
audioRecord?.stop() // 必须先停止再释放 }
} catch (e: Exception) { /**
Log.e("AudioConfig", "Error releasing resources: ${e.message}") * 关闭录音
} finally { */
// 确保恢复原始音频状态 fun stopRecord(isSave: Boolean) {
restoreOriginalAudioState() Log.i(tag, "关闭录音:")
}
recordfile!!.isPause = false
recordfile!!.closeFile(isSave)
} }
/** /**
* 应用音频效果 - 总是启用所有音频增强功能 * 麦克风流 - 拉流模式
* 实现PullAudioInputStreamCallback,为Azure SDK提供音频数据
*/ */
private fun applyAudioEffects() { inner class AudioStream {
val sessionId = audioRecord?.audioSessionId ?: return
try { private var echoCanceler: AcousticEchoCanceler? = null
// 设置音频模式为通信模式 private var noiseSuppressor: NoiseSuppressor? = null
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION private var automaticGainControl: AutomaticGainControl? = null
// 启用声学回音消除
if (AcousticEchoCanceler.isAvailable()) {
echoCanceler = AcousticEchoCanceler.create(sessionId)
echoCanceler?.enabled = true
Log.i(tag, "声学回音消除已启用")
} else {
Log.w(tag, "设备不支持声学回音消除")
}
// 启用噪声抑制 var audioRecord: AudioRecord? = null
if (NoiseSuppressor.isAvailable()) { private var captureThread: Thread? = null
noiseSuppressor = NoiseSuppressor.create(sessionId) var pushAudioStream: PushAudioInputStream? = null
noiseSuppressor?.enabled = true
Log.i(tag, "噪声抑制已启用")
// 音频配置
private val channelConfig = AudioFormat.CHANNEL_IN_MONO
private val audioFormat = AudioFormat.ENCODING_PCM_16BIT
/**
* 开始麦克风捕获
*/
fun initAudioRecord() {
val format = getOptimalAudioFormat()
pushAudioStream = AudioInputStream.createPushStream(format)
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100)
// 查找设备支持的最佳采样率
val sampleRate = supportedSampleRates.firstOrNull { rate ->
val bufferSize = AudioRecord.getMinBufferSize(
rate,
channelConfig,
audioFormat
)
bufferSize > 0 // 返回正值表示支持
} ?: 16000 // 默认回退值
// 计算最小缓冲区大小(避免出现音频欠载)
val minBufferSize = AudioRecord.getMinBufferSize(
sampleRate,
channelConfig,
audioFormat
)
// 创建录音对象
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
Log.d("AzureASR", "android.os.Build.VERSION.SDK_INT")
val format = AudioFormat.Builder()
.setSampleRate(sampleRate)
.setEncoding(audioFormat)
.setChannelMask(channelConfig)
.build()
audioRecord = AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
.setAudioFormat(format)
.setBufferSizeInBytes(minBufferSize * 2)
.build()
} else { } else {
Log.w(tag, "设备不支持噪声抑制") // 创建 AudioRecord 对象
audioRecord = AudioRecord(
MediaRecorder.AudioSource.VOICE_RECOGNITION, // 语音识别专用音源(自带降噪/回声消除)
sampleRate,
channelConfig,
audioFormat,
minBufferSize * 2 // 使用两倍缓冲区防止溢出
)
} }
// 启用自动增益控制
if (AutomaticGainControl.isAvailable()) { // 检查初始化状态
automaticGainControl = AutomaticGainControl.create(sessionId) if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) {
automaticGainControl?.enabled = true throw IllegalStateException("AudioRecord初始化失败")
Log.i(tag, "自动增益控制已启用")
} else {
Log.w(tag, "设备不支持自动增益控制")
} }
Log.i(tag, "回音消除和音频增强功能已全部启用") startCaptureThread() // 再启动数据读取线程
} catch (e: Exception) {
Log.e(tag, "设置音频效果失败: ${e.message}")
} }
}
/** // 3. 单独封装线程启动逻辑
* 释放音频资源 fun startCaptureThread() {
*/ // 开始录音
private fun releaseAudioResources() { audioRecord?.startRecording()
try { captureThread = Thread {
// 停止录音 // 根据实际采样率计算缓冲区大小
if (audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) { val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100)
audioRecord?.stop() var bufferSize = 0
} // 查找设备支持的最佳采样率
val sampleRate = supportedSampleRates.firstOrNull { rate ->
bufferSize = AudioRecord.getMinBufferSize(
rate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
bufferSize > 0 // 返回正值表示支持
} ?: 16000 // 默认回退值
val buffer = ByteArray(bufferSize)
while (!Thread.interrupted() && audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
if (!isPaused) {
// 读取音频
val bytesRead = audioRecord?.read(buffer, 0, buffer.size) ?: 0
if (bytesRead > 0) {
// 写入Azure流
pushAudioStream?.write(buffer)
// 保存录音
recordfile?.saveAudioDataToWav(buffer)
}
}
}
}.apply { start() }
}
// 释放回音消除器 /**
echoCanceler?.let { * 停止麦克风捕获并释放所有相关资源
it.enabled = false */
it.release() fun stopMicrophoneCapture() {
echoCanceler = null try {
} if (captureThread != null) {
// 中断并等待捕获线程结束
captureThread?.interrupt()
captureThread?.join(300) // 最多等待300ms
// 停止录音并释放AudioRecord资源
audioRecord?.stop() // 必须先停止再释放
isPaused= true
}
// 释放噪声抑制器
noiseSuppressor?.let {
it.enabled = false
it.release()
noiseSuppressor = null
}
// 释放自动增益控制 } catch (e: Exception) {
automaticGainControl?.let { Log.e("AudioConfig", "Error releasing resources: ${e.message}")
it.enabled = false } finally {
it.release() // 确保恢复原始音频状态
automaticGainControl = null // restoreOriginalAudioState()
} }
}
// 恢复音频模式
this@AzureAsrHelper.audioManager?.mode = this@AzureAsrHelper.originalAudioMode /**
* 释放音频资源
*/
fun releaseAudioResources() {
try {
isPaused= true
// 停止录音
if (audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
audioRecord?.stop()
}
// 释放录音实例 // // 释放回音消除器
audioRecord?.release() // echoCanceler?.let {
audioRecord = null // it.enabled = false
} catch (e: Exception) { // it.release()
Log.e(tag, "释放音频资源失败: ${e.message}") // echoCanceler = null
e.printStackTrace() // }
// // 释放噪声抑制器
// noiseSuppressor?.let {
// it.enabled = false
// it.release()
// noiseSuppressor = null
// }
// // 释放自动增益控制
// automaticGainControl?.let {
// it.enabled = false
// it.release()
// automaticGainControl = null
// }
// 恢复音频模式
this@AzureAsrHelper.audioManager?.mode = this@AzureAsrHelper.originalAudioMode
// 释放录音实例
audioRecord?.release()
audioRecord = null
} catch (e: Exception) {
Log.e(tag, "释放音频资源失败: ${e.message}")
e.printStackTrace()
}
} }
}
// /** }
// * 麦克风流 - 拉流模式
// * 实现PullAudioInputStreamCallback,为Azure SDK提供音频数据
// */
// private inner class MicrophoneStream : PullAudioInputStreamCallback() {
// private var audioRecord: AudioRecord? = null
// private var echoCanceler: AcousticEchoCanceler? = null
// private var noiseSuppressor: NoiseSuppressor? = null
// private var automaticGainControl: AutomaticGainControl? = null
// // 音频配置
// private val sampleRate = 16000
// private val channelConfig = AudioFormat.CHANNEL_IN_MONO
// private val audioFormat = AudioFormat.ENCODING_PCM_16BIT
// // 优化:减少缓冲区大小以降低延迟
// private val bufferSize = AudioRecord.getMinBufferSize(
// sampleRate, channelConfig, audioFormat
// ).let { if (it < 0) 2048 else it }
// init {
// initMicrophone()
// }
// /**
// * 初始化麦克风
// */
// private fun initMicrophone() {
// try {
// // 创建录音对象
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// val format = AudioFormat.Builder()
// .setSampleRate(sampleRate)
// .setEncoding(audioFormat)
// .setChannelMask(channelConfig)
// .build()
// audioRecord = AudioRecord.Builder()
// .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
// .setAudioFormat(format)
// .setBufferSizeInBytes(bufferSize)
// .build()
// } else {
// audioRecord = AudioRecord(
// MediaRecorder.AudioSource.VOICE_COMMUNICATION,
// sampleRate,
// channelConfig,
// audioFormat,
// bufferSize
// )
// }
// // 检查初始化状态
// if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) {
// throw IllegalStateException("AudioRecord初始化失败")
// }
// // 应用音频效果
// applyAudioEffects()
// // 开始录音
// audioRecord?.startRecording()
// // 检查录音状态
// if (audioRecord?.recordingState != AudioRecord.RECORDSTATE_RECORDING) {
// throw IllegalStateException("录音启动失败")
// }
// } catch (e: Exception) {
// e.printStackTrace()
// }
// }
// /**
// * 应用音频效果 - 总是启用所有音频增强功能
// */
// private fun applyAudioEffects() {
// val sessionId = audioRecord?.audioSessionId ?: return
// try {
// // 设置音频模式为通信模式
// this@AzureAsrHelper.audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
// // 启用声学回音消除
// if (AcousticEchoCanceler.isAvailable()) {
// echoCanceler = AcousticEchoCanceler.create(sessionId)
// echoCanceler?.enabled = true
// Log.i(tag, "声学回音消除已启用")
// } else {
// Log.w(tag, "设备不支持声学回音消除")
// }
// // 启用噪声抑制
// if (NoiseSuppressor.isAvailable()) {
// noiseSuppressor = NoiseSuppressor.create(sessionId)
// noiseSuppressor?.enabled = true
// Log.i(tag, "噪声抑制已启用")
// } else {
// Log.w(tag, "设备不支持噪声抑制")
// }
// // 启用自动增益控制
// if (AutomaticGainControl.isAvailable()) {
// automaticGainControl = AutomaticGainControl.create(sessionId)
// automaticGainControl?.enabled = true
// Log.i(tag, "自动增益控制已启用")
// } else {
// Log.w(tag, "设备不支持自动增益控制")
// }
// Log.i(tag, "回音消除和音频增强功能已全部启用")
// } catch (e: Exception) {
// Log.e(tag, "设置音频效果失败: ${e.message}")
// }
// }
// /**
// * 获取音频格式
// */
// fun getFormat(): AudioStreamFormat =
// AudioStreamFormat.getWaveFormatPCM(16000.toLong(), 16.toShort(), 1.toShort())
// /**
// * Azure SDK 调用此方法获取音频数据
// * 这是拉流模式的核心方法,由SDK调用以获取音频数据
// */
// /**
// * 优化:简化音频数据读取(移除文件保存)
// */
// override fun read(buffer: ByteArray): Int {
// try {
// val result = audioRecord?.read(buffer, 0, buffer.size) ?: -1
// if (result < 0) {
// Log.e(tag, "读取音频数据失败: $result")
// return 0
// }
// recordfile?.saveAudioDataToWav(buffer)
// return result
// } catch (e: Exception) {
// Log.e(tag, "读取音频异常: ${e.message}")
// return 0
// }
// }
// /**
// * 优化:简化资源关闭
// */
// override fun close() {
// releaseAudioResources()
// }
// /**
// * 释放音频资源
// */
// private fun releaseAudioResources() {
// try {
// // 停止录音
// if (audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
// audioRecord?.stop()
// }
// // 释放回音消除器
// echoCanceler?.let {
// it.enabled = false
// it.release()
// echoCanceler = null
// }
// // 释放噪声抑制器
// noiseSuppressor?.let {
// it.enabled = false
// it.release()
// noiseSuppressor = null
// }
// // 释放自动增益控制
// automaticGainControl?.let {
// it.enabled = false
// it.release()
// automaticGainControl = null
// }
// // 恢复音频模式
// this@AzureAsrHelper.audioManager?.mode = this@AzureAsrHelper.originalAudioMode
// // 释放录音实例
// audioRecord?.release()
// audioRecord = null
// } catch (e: Exception) {
// Log.e(tag, "释放音频资源失败: ${e.message}")
// e.printStackTrace()
// }
// }
// }
/** /**
* 外部音频拉流 * 外部音频拉流

6
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -256,13 +256,12 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
} }
"setPause" -> { "setPause" -> {
azureAsrHelper.stopAudioPause(); azureAsrHelper.audioPause();
return return
} }
"setcontinue" -> { "setcontinue" -> {
// azureAsrHelper.stopAudioPause(); azureAsrHelper.audioContinue();
azureAsrHelper.setAudiocontinue();
return return
} }
@ -272,7 +271,6 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
} }
"restoreOriginalAudioState" -> { "restoreOriginalAudioState" -> {
// azureAsrHelper.stopAudioPause();
azureAsrHelper.restoreOriginalAudioState(); azureAsrHelper.restoreOriginalAudioState();
return return
} }

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

@ -578,15 +578,14 @@ object BleService {
// 根据特征UUID区分处理 // 根据特征UUID区分处理
when (c.uuid) { when (c.uuid) {
// 音频特征数据 // 音频特征数据
BleConst.RECEIVE_AUDIO_CHAR_UUID1 -> { // BleConst.RECEIVE_AUDIO_CHAR_UUID1 -> {
Log.i(TAG, "RECEIVE_AUDIO_CHAR_UUID1") // Log.i(TAG, "RECEIVE_AUDIO_CHAR_UUID1")
processAudioData(data) // processAudioData(data)
} // }
// 音频特征数据 // 音频特征数据
BleConst.RECEIVE_AUDIO_CHAR_UUID -> { BleConst.RECEIVE_AUDIO_CHAR_UUID -> {
Log.i(TAG, "RECEIVE_AUDIO_CHAR_UUID")
processAudioData(data) processAudioData(data)
} }
// 通知特征数据(命令和控制) // 通知特征数据(命令和控制)

Loading…
Cancel
Save