Browse Source

add

newdev_shunjiawei
wolfplus 2 years ago
parent
commit
59f8a56740
  1. 2
      ios/Runner/AppDelegate.swift
  2. 145
      ios/Runner/AudioSessionManager.swift
  3. 33
      ios/Runner/AzureAsrHelper.swift
  4. 4
      ios/Runner/AzureTtsHelper.swift
  5. 40
      lib/modules/chat/controllers/chat_controller.dart
  6. 16
      lib/modules/translation/controllers/translation_controller.dart

2
ios/Runner/AppDelegate.swift

@ -588,7 +588,7 @@ class AsrStreamHandler: NSObject, FlutterStreamHandler, AsrEventHandler {
// 实现AsrEventHandler协议
func sendEvent(_ event: [String: Any]) {
NSLog("AsrStreamHandler: 收到事件 \(event)")
// NSLog("AsrStreamHandler: 收到事件 \(event)")
DispatchQueue.main.async { [weak self] in
self?.eventSink?(event)
}

145
ios/Runner/AudioSessionManager.swift

@ -264,12 +264,25 @@ import UIKit
if !isConfigured {
NSLog("[AudioSessionManager] 警告: 尝试激活未配置的音频会话,应用默认配置")
// 未配置,应用默认配置
try configureSession(
category: .playAndRecord,
mode: .spokenAudio,
options: [.allowBluetooth, .allowBluetoothA2DP, .duckOthers, .defaultToSpeaker],
force: true
// try configureSession(
// category: .playAndRecord,
// mode: .spokenAudio,
// options: [.allowBluetooth, .allowBluetoothA2DP, .duckOthers, .defaultToSpeaker],
// force: true
// )
try configureSession(
category: .playAndRecord, // 允许同时录音和播放
mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果
options: [
.allowBluetooth, // 允许蓝牙设备
.defaultToSpeaker, // 默认使用扬声器
.mixWithOthers // 允许与其他应用混音
],
force: false // 是否强制重新配置
)
} else if !isActive {
NSLog("[AudioSessionManager] 激活已配置的音频会话")
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
@ -289,12 +302,24 @@ import UIKit
/// 重新配置当前会话
@objc func forceReconfigure() throws {
NSLog("[AudioSessionManager] 强制重新配置音频会话")
try configureSession(
category: currentCategory ?? .playAndRecord,
mode: currentMode ?? .default,
options: currentOptions ?? [.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker],
force: true
)
// try configureSession(
// category: currentCategory ?? .playAndRecord,
// mode: currentMode ?? .default,
// options: currentOptions ?? [.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker],
// force: true
// )
try configureSession(
category: .playAndRecord, // 允许同时录音和播放
mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果
options: [
.allowBluetooth, // 允许蓝牙设备
.defaultToSpeaker, // 默认使用扬声器
.mixWithOthers // 允许与其他应用混音
],
force: false // 是否强制重新配置
)
}
// MARK: - 状态检查
@ -420,60 +445,60 @@ import UIKit
/// 为Azure语音识别专门配置的音频会话
/// 此函数优化Azure语音识别的音频处理配置,提供更强的回音消除能力
/// - Parameters:
/// - force: 是否强制重新配置
/// - configureAdditionalSettings: 配置完成后进行额外的音频设置
/// - Returns: 配置是否成功
@objc func configureForAzureSpeechRecognition(force: Bool = false, configureAdditionalSettings: Bool = true) -> Bool {
NSLog("[AudioSessionManager] 配置Azure语音识别的音频会话")
do {
// 配置音频会话以优化语音识别
try configureSession(
category: .playAndRecord, // 允许同时录音和播放
mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果
options: [
.allowBluetooth, // 允许蓝牙设备
.defaultToSpeaker, // 默认使用扬声器
.mixWithOthers // 允许与其他应用混音
],
force: force // 是否强制重新配置
)
// /// 为Azure语音识别专门配置的音频会话
// /// 此函数优化Azure语音识别的音频处理配置,提供更强的回音消除能力
// /// - Parameters:
// /// - force: 是否强制重新配置
// /// - configureAdditionalSettings: 配置完成后进行额外的音频设置
// /// - Returns: 配置是否成功
// @objc func configureForAzureSpeechRecognition(force: Bool = false, configureAdditionalSettings: Bool = true) -> Bool {
// NSLog("[AudioSessionManager] 配置Azure ASR语音识别的音频会话")
// do {
// // 配置音频会话以优化语音识别
// try configureSession(
// category: .playAndRecord, // 允许同时录音和播放
// mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果
// options: [
// .allowBluetooth, // 允许蓝牙设备
// .defaultToSpeaker, // 默认使用扬声器
// .mixWithOthers // 允许与其他应用混音
// ],
// force: force // 是否强制重新配置
// )
// 额外的优化配置
if configureAdditionalSettings {
// 获取当前是否使用耳机
let currentRoute = audioSession.currentRoute
let hasHeadphones = currentRoute.outputs.contains {
$0.portType == .headphones || $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP
}
// // 额外的优化配置
// if configureAdditionalSettings {
// // 获取当前是否使用耳机
// let currentRoute = audioSession.currentRoute
// let hasHeadphones = currentRoute.outputs.contains {
// $0.portType == .headphones || $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP
// }
// 设置采样率为16kHz(Azure Speech API推荐)
try audioSession.setPreferredSampleRate(16000.0)
// // 设置采样率为16kHz(Azure Speech API推荐)
// try audioSession.setPreferredSampleRate(16000.0)
// 设置较小的缓冲区大小以减少延迟
try audioSession.setPreferredIOBufferDuration(0.01)
// // 设置较小的缓冲区大小以减少延迟
// try audioSession.setPreferredIOBufferDuration(0.01)
// 根据是否有耳机连接调整输入增益
if !hasHeadphones {
// 无耳机时降低输入增益以减少回音
try audioSession.setInputGain(0.8)
NSLog("[AudioSessionManager] 启用扬声器回音消除优化")
} else {
// 使用耳机时可以使用较高增益
try audioSession.setInputGain(1.0)
NSLog("[AudioSessionManager] 检测到耳机连接,应用耳机模式")
}
}
// // 根据是否有耳机连接调整输入增益
// if !hasHeadphones {
// // 无耳机时降低输入增益以减少回音
// try audioSession.setInputGain(0.8)
// NSLog("[AudioSessionManager] 启用扬声器回音消除优化")
// } else {
// // 使用耳机时可以使用较高增益
// try audioSession.setInputGain(1.0)
// NSLog("[AudioSessionManager] 检测到耳机连接,应用耳机模式")
// }
// }
NSLog("[AudioSessionManager] 已成功配置Azure语音识别的音频会话")
return true
} catch {
NSLog("[AudioSessionManager] 配置Azure语音识别的音频会话失败: \(error.localizedDescription)")
return false
}
}
// NSLog("[AudioSessionManager] 已成功配置Azure语音识别的音频会话")
// return true
// } catch {
// NSLog("[AudioSessionManager] 配置Azure语音识别的音频会话失败: \(error.localizedDescription)")
// return false
// }
// }
@objc func configureForVoiceInteraction(force: Bool = false, configureAdditionalSettings: Bool = true) -> Bool {

33
ios/Runner/AzureAsrHelper.swift

@ -8,6 +8,9 @@ import AudioToolbox
class AzureAsrHelper: NSObject {
// MARK: - 属性
/// 是否使用自定义音频处理器 - 设置为false时将使用系统默认麦克风输入
private let useCustomAudioProcessor = false
/// 事件处理回调
private var eventHandler: (([String: Any]) -> Void)?
@ -115,12 +118,18 @@ class AzureAsrHelper: NSObject {
// 设置音频输入参数
try setupAudioSession()
// 创建自定义音频流和处理器,替代默认的麦克风输入
pushStream = try SPXPushAudioInputStream()
audioConfig = try SPXAudioConfiguration(streamInput: pushStream!)
// 初始化自定义音频处理器
audioProcessor = CustomAudioProcessor()
// 根据设置决定是否使用自定义音频处理器
if useCustomAudioProcessor {
// 创建自定义音频流和处理器,替代默认的麦克风输入
pushStream = try SPXPushAudioInputStream()
audioConfig = try SPXAudioConfiguration(streamInput: pushStream!)
// 初始化自定义音频处理器
audioProcessor = CustomAudioProcessor()
} else {
// 使用默认麦克风输入
audioConfig = try SPXAudioConfiguration()
}
// 设置语言配置
if isAutoDetectLanguage {
@ -160,7 +169,7 @@ class AzureAsrHelper: NSObject {
print("[AzureAsrHelper] 开始配置音频会话...")
// 使用AudioSessionManager配置音频会话,使用专门为Azure ASR优化的配置
let success = audioSessionManager.configureForAzureSpeechRecognition(force: true)
let success = audioSessionManager.configureForVoiceInteraction()
if !success {
print("[AzureAsrHelper] 警告: 通过AudioSessionManager配置音频会话失败")
}
@ -480,6 +489,11 @@ class AzureAsrHelper: NSObject {
/// 开始音频处理
private func startAudioProcessing() {
// 如果未使用自定义音频处理器,跳过
if !useCustomAudioProcessor {
return
}
guard !isProcessingAudio, let audioProcessor = audioProcessor else { return }
isProcessingAudio = true
@ -516,6 +530,11 @@ class AzureAsrHelper: NSObject {
/// 停止音频处理
private func stopAudioProcessing() {
// 如果未使用自定义音频处理器,跳过
if !useCustomAudioProcessor {
return
}
// 停止定时器
audioProcessingTimer?.invalidate()
audioProcessingTimer = nil

4
ios/Runner/AzureTtsHelper.swift

@ -258,7 +258,7 @@ class AzureTtsHelper: NSObject {
_isSpeaking = true
eventHandler?(["type": "started"])
// Task {
Task {
do {
// 使用异步方法进行合成并直接播放
_ = try synthesizer.startSpeakingSsml(text)
@ -270,7 +270,7 @@ class AzureTtsHelper: NSObject {
self.eventHandler?(["type": "error", "message": "语音合成失败: \(error.localizedDescription)"])
}
}
// }
}
return true
}

40
lib/modules/chat/controllers/chat_controller.dart

@ -48,6 +48,9 @@ class ChatController extends GetxController {
// 临时消息索引,用于更新临时消息
int _tempMessageIndex = -1;
// 防抖计时器
Timer? _scrollDebounceTimer;
// 流式响应控制
StreamSubscription? _aiResponseSubscription;
@ -201,6 +204,7 @@ class ChatController extends GetxController {
_recognitionSubscription?.cancel();
_voiceInteractionSubscription?.cancel();
_autoStopTimer?.cancel();
_scrollDebounceTimer?.cancel(); // 清理防抖计时器
// 停止TTS
_ttsService.stop();
@ -209,22 +213,28 @@ class ChatController extends GetxController {
_asrService.stopContinuousRecognition();
}
// 保存当前聊天记录
if (agent.id != null) {
_historyService.saveHistory(agent.id!, messages.toList());
}
super.onClose();
}
/// 滚动到列表底部方法
/// 滚动到列表底部方法,添加防抖功能
void _scrollToBottom() {
if (scrollController.hasClients && messages.isNotEmpty) {
try {
scrollController.animateTo(
scrollController.position.maxScrollExtent + 20,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} catch (e) {
Logger.error('滚动到底部失败', e);
if (scrollController.hasClients && messages.isNotEmpty) {
try {
scrollController.animateTo(
scrollController.position.maxScrollExtent + 30,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} catch (e) {
Logger.error('滚动到底部失败', e);
}
}
}
}
/// 中断当前响应
@ -539,9 +549,15 @@ class ChatController extends GetxController {
messages.add(tempMessage);
_tempMessageIndex = messages.length - 1;
messages.refresh();
_scrollToBottom();
}
// 取消之前的计时器
_scrollDebounceTimer?.cancel();
_scrollToBottom();
// 设置新的防抖计时器,延迟100毫秒执行滚动
_scrollDebounceTimer = Timer(Duration(milliseconds: 500), () {
_scrollToBottom();
});
}
// 处理语音识别最终结果

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

@ -244,7 +244,7 @@ class TranslationController extends GetxController {
// 如果还没有翻译结果,尝试翻译
if (translationHistory.last.translatedText.isEmpty) {
await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp);
await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: true);
}
saveTranslationHistory();
@ -288,7 +288,7 @@ class TranslationController extends GetxController {
_translationDebounceTimer?.cancel();
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () {
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) {
translateText(translationHistory.last.sourceText, translationHistory.last.timestamp);
translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: false);
}
});
}
@ -307,7 +307,7 @@ class TranslationController extends GetxController {
saveTranslationHistory();
// 使用项目的时间戳进行翻译
translateText(text, translationHistory.last.timestamp);
translateText(text, translationHistory.last.timestamp, isFinal: true);
} else {
// 如果最后一项不是中间结果,创建新项
final newItem = TranslationItem(
@ -326,13 +326,14 @@ class TranslationController extends GetxController {
_scrollToBottom();
saveTranslationHistory();
// 使用新项目的时间戳进行翻译
translateText(text, newItem.timestamp);
translateText(text, newItem.timestamp, isFinal: true);
}
}
// 翻译文本
Future<String?> translateText(String sourceText, DateTime timestamp) async {
Future<String?> translateText(String sourceText, DateTime timestamp, {bool isFinal = false}) async {
if (sourceText.isEmpty) return null;
isTranslating.value = true;
@ -367,8 +368,9 @@ class TranslationController extends GetxController {
// 更新后滚动到底部
_scrollToBottom();
// 播放TTS
if (isTtsEnabled.value && !translationHistory[index].isIntermediate) {
// 播放TTS - 只在最终结果时播放
if (isTtsEnabled.value && isFinal) {
// Logger.info('播放翻译文本: ${translationResult}');
await playTranslatedText(translationResult);
}
} else {

Loading…
Cancel
Save