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协议 // 实现AsrEventHandler协议
func sendEvent(_ event: [String: Any]) { func sendEvent(_ event: [String: Any]) {
NSLog("AsrStreamHandler: 收到事件 \(event)") // NSLog("AsrStreamHandler: 收到事件 \(event)")
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.eventSink?(event) self?.eventSink?(event)
} }

145
ios/Runner/AudioSessionManager.swift

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

33
ios/Runner/AzureAsrHelper.swift

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

4
ios/Runner/AzureTtsHelper.swift

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

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

@ -48,6 +48,9 @@ class ChatController extends GetxController {
// 临时消息索引,用于更新临时消息 // 临时消息索引,用于更新临时消息
int _tempMessageIndex = -1; int _tempMessageIndex = -1;
// 防抖计时器
Timer? _scrollDebounceTimer;
// 流式响应控制 // 流式响应控制
StreamSubscription? _aiResponseSubscription; StreamSubscription? _aiResponseSubscription;
@ -201,6 +204,7 @@ class ChatController extends GetxController {
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
_voiceInteractionSubscription?.cancel(); _voiceInteractionSubscription?.cancel();
_autoStopTimer?.cancel(); _autoStopTimer?.cancel();
_scrollDebounceTimer?.cancel(); // 清理防抖计时器
// 停止TTS // 停止TTS
_ttsService.stop(); _ttsService.stop();
@ -209,22 +213,28 @@ class ChatController extends GetxController {
_asrService.stopContinuousRecognition(); _asrService.stopContinuousRecognition();
} }
// 保存当前聊天记录
if (agent.id != null) {
_historyService.saveHistory(agent.id!, messages.toList());
}
super.onClose(); super.onClose();
} }
/// 滚动到列表底部方法 /// 滚动到列表底部方法,添加防抖功能
void _scrollToBottom() { void _scrollToBottom() {
if (scrollController.hasClients && messages.isNotEmpty) {
try { if (scrollController.hasClients && messages.isNotEmpty) {
scrollController.animateTo( try {
scrollController.position.maxScrollExtent + 20, scrollController.animateTo(
duration: Duration(milliseconds: 300), scrollController.position.maxScrollExtent + 30,
curve: Curves.easeOut, duration: Duration(milliseconds: 300),
); curve: Curves.easeOut,
} catch (e) { );
Logger.error('滚动到底部失败', e); } catch (e) {
Logger.error('滚动到底部失败', e);
}
} }
}
} }
/// 中断当前响应 /// 中断当前响应
@ -539,9 +549,15 @@ class ChatController extends GetxController {
messages.add(tempMessage); messages.add(tempMessage);
_tempMessageIndex = messages.length - 1; _tempMessageIndex = messages.length - 1;
messages.refresh(); 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) { if (translationHistory.last.translatedText.isEmpty) {
await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp); await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: true);
} }
saveTranslationHistory(); saveTranslationHistory();
@ -288,7 +288,7 @@ class TranslationController extends GetxController {
_translationDebounceTimer?.cancel(); _translationDebounceTimer?.cancel();
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { _translationDebounceTimer = Timer(const Duration(milliseconds: 300), () {
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { 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(); saveTranslationHistory();
// 使用项目的时间戳进行翻译 // 使用项目的时间戳进行翻译
translateText(text, translationHistory.last.timestamp); translateText(text, translationHistory.last.timestamp, isFinal: true);
} else { } else {
// 如果最后一项不是中间结果,创建新项 // 如果最后一项不是中间结果,创建新项
final newItem = TranslationItem( final newItem = TranslationItem(
@ -326,13 +326,14 @@ class TranslationController extends GetxController {
_scrollToBottom(); _scrollToBottom();
saveTranslationHistory(); 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; if (sourceText.isEmpty) return null;
isTranslating.value = true; isTranslating.value = true;
@ -367,8 +368,9 @@ class TranslationController extends GetxController {
// 更新后滚动到底部 // 更新后滚动到底部
_scrollToBottom(); _scrollToBottom();
// 播放TTS // 播放TTS - 只在最终结果时播放
if (isTtsEnabled.value && !translationHistory[index].isIntermediate) { if (isTtsEnabled.value && isFinal) {
// Logger.info('播放翻译文本: ${translationResult}');
await playTranslatedText(translationResult); await playTranslatedText(translationResult);
} }
} else { } else {

Loading…
Cancel
Save