diff --git a/lib/data/services/asr_service.dart b/lib/data/services/asr_service.dart index 70f88353b..f0c372115 100644 --- a/lib/data/services/asr_service.dart +++ b/lib/data/services/asr_service.dart @@ -26,11 +26,16 @@ abstract class AsrService { /// 停止连续语音识别 Future stopContinuousRecognition(); + /// 停止音频输入 Future setPause(); - Future setcontinue(); + /// 继续音频输入 + Future setContinue(); + + /// 恢复原始音频状态 Future restoreOriginalAudioState(); + /// 禁用蓝牙音频 Future disableBluetoothAudio(); /// 检查连续识别是否活跃 diff --git a/lib/data/services/speech_impl/azure_asr_service.dart b/lib/data/services/speech_impl/azure_asr_service.dart index cc5ae9746..16261533c 100644 --- a/lib/data/services/speech_impl/azure_asr_service.dart +++ b/lib/data/services/speech_impl/azure_asr_service.dart @@ -429,7 +429,7 @@ class AzureAsrService extends GetxService implements AsrService { } @override - Future setcontinue() async { + Future setContinue() async { // TODO: implement setcontinue try { final bool result = await _channel.invokeMethod('setcontinue'); diff --git a/lib/data/services/speech_impl/volcano_asr_api_service.dart b/lib/data/services/speech_impl/volcano_asr_api_service.dart index 3fbaa53ef..183f899db 100644 --- a/lib/data/services/speech_impl/volcano_asr_api_service.dart +++ b/lib/data/services/speech_impl/volcano_asr_api_service.dart @@ -849,7 +849,7 @@ class VolcanoAsrApiService implements AsrService { } @override - Future setcontinue() { + Future setContinue() { // TODO: implement setcontinue throw UnimplementedError(); } diff --git a/lib/data/services/speech_impl/volcano_asr_service.dart b/lib/data/services/speech_impl/volcano_asr_service.dart index a7b3f541d..871dccb99 100644 --- a/lib/data/services/speech_impl/volcano_asr_service.dart +++ b/lib/data/services/speech_impl/volcano_asr_service.dart @@ -384,7 +384,7 @@ class VolcanoAsrService extends GetxService implements AsrService { } @override - Future setcontinue() { + Future setContinue() { // TODO: implement setcontinue throw UnimplementedError(); } diff --git a/lib/data/services/speech_impl/xunfei_asr_service.dart b/lib/data/services/speech_impl/xunfei_asr_service.dart index 6b5450761..5dc8d3a31 100644 --- a/lib/data/services/speech_impl/xunfei_asr_service.dart +++ b/lib/data/services/speech_impl/xunfei_asr_service.dart @@ -308,7 +308,7 @@ class XunfeiAsrService extends GetxService implements AsrService { } @override - Future setcontinue() { + Future setContinue() { // TODO: implement setcontinue throw UnimplementedError(); } diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index 877ddad5b..dd1fc980b 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -37,10 +37,13 @@ class TranslationController extends GetxController { final bleManager = Get.find(); // 音频输入源 bool _audioSourceType = false; - // 倒计时器 - bool istts = false; +//是否开启播放 + bool isPlayback = false; + //录音计时器 final RxString recordDurationText = '00:00'.obs; + //记录计时器 Timer? _recordTimer; + //秒 int _seconds = 0; // 存储相关 static const String _historyKey = 'translation_history'; @@ -99,23 +102,24 @@ class TranslationController extends GetxController { activeSpeaker.value = id; // 监听识别结果 if (activeSpeaker.value != 0) { - istts = false; - _asrService.setcontinue(); + isPlayback = false; + _asrService.setContinue(); lastActiveSpeaker.value = id; } else { _asrService.setPause(); - if (fTFTranslationResult != '' && !istts) { - istts = true; + if (fTFTranslationResult != '' && !isPlayback) { + isPlayback = true; await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value); + await Future.delayed(const Duration(milliseconds: 1300)); await playTranslatedText(fTFTranslationResult!); fTFTranslationResult = ''; } } } - // 切换TTS功能 + // 禁用蓝牙音频 void disableBluetoothAudio() => _asrService.disableBluetoothAudio(); - // 切换TTS功能 + //恢复原始音频状态 void restoreOriginalAudioState() => _asrService.restoreOriginalAudioState(); // 兼容旧版视图的计算属性 @@ -379,7 +383,8 @@ class TranslationController extends GetxController { if (currentMode.value == 'faceToFace') { _asrService.setPause(); } else { - _asrService.setcontinue(); + await _ttsService.setAudioOutputDevice(0); + _asrService.setContinue(); } isRecognizing.value = true; if (isRecording.value && isRecognizing.value) { @@ -577,19 +582,20 @@ class TranslationController extends GetxController { fTFTranslationResult = (fTFTranslationResult ?? '') + translationResult; - if (activeSpeaker.value == 0 && !istts) { - istts = true; + if (activeSpeaker.value == 0 && + !isPlayback && + currentMode.value == 'faceToFace') { + isPlayback = true; await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value); + await Future.delayed(const Duration(milliseconds: 1300)); await playTranslatedText(fTFTranslationResult!); fTFTranslationResult = ''; - ; } } // 播放TTS - 只在最终结果时播放 if (isTtsEnabled.value && isFinal && currentMode.value != 'faceToFace') { - _ttsService.setAudioOutputDevice(activeSpeaker.value); await playTranslatedText(translationResult); } } else { diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt index a450c0d8a..3c4c6fcb0 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt @@ -80,6 +80,7 @@ class AzureAsrHelper(private val context: Context) { /** 使用外部提供的音频数据 */ EXTERNAL } + /** * 获取设备支持的最佳音频格式 * 优先选择16000Hz,若不支持则降级到8000Hz @@ -103,6 +104,7 @@ class AzureAsrHelper(private val context: Context) { // 创建对应的音频格式 return AudioStreamFormat.getWaveFormatPCM(sampleRate.toLong(), 16, 1) } + /** * 初始化Azure语音服务 * @@ -391,8 +393,7 @@ class AzureAsrHelper(private val context: Context) { audioRecord?.startRecording() startCaptureThread() // 再启动数据读取线程 isContinuousRecognitionActive = true -Log.d(tag, "识别器创建成功: ${recognizer != null}") -Log.d(tag, "开始识别任务: recognizeOnce 或 startContinuousRecognition 被调用") + return true } catch (e: Exception) { @@ -729,8 +730,8 @@ Log.d(tag, "开始识别任务: recognizeOnce 或 startContinuousRecognition 被 * 开始麦克风捕获 */ private fun initAudioRecord() { - // 获取已确定的采样率 - val format = getOptimalAudioFormat() + // 获取已确定的采样率 + val format = getOptimalAudioFormat() val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100) // 查找设备支持的最佳采样率 @@ -751,58 +752,75 @@ Log.d(tag, "开始识别任务: recognizeOnce 或 startContinuousRecognition 被 channelConfig, audioFormat ) - - // 创建 AudioRecord 对象 - audioRecord = AudioRecord( - MediaRecorder.AudioSource.VOICE_RECOGNITION, // 语音识别专用音源(自带降噪/回声消除) - sampleRate, - channelConfig, - audioFormat, - minBufferSize * 2 // 使用两倍缓冲区防止溢出 - ) - - - // 应用音频效果 - applyAudioEffects() - } - - // 3. 单独封装线程启动逻辑 - private fun startCaptureThread() { - captureThread = Thread { - // 根据实际采样率计算缓冲区大小 - val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100) - var bufferSize = 0 - // 查找设备支持的最佳采样率 - val sampleRate = supportedSampleRates.firstOrNull { rate -> - bufferSize = AudioRecord.getMinBufferSize( - rate, - AudioFormat.CHANNEL_IN_MONO, - AudioFormat.ENCODING_PCM_16BIT +// 创建录音对象 + 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 // 使用两倍缓冲区防止溢出 ) - bufferSize > 0 // 返回正值表示支持 - } ?: 16000 // 默认回退值 - // (sampleRate * 0.032).toInt() * 2 // 32ms数据量 - - val buffer = ByteArray(bufferSize) - while (!Thread.interrupted() && audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) { + } + - // 读取音频 - val bytesRead = audioRecord?.read(buffer, 0, buffer.size) ?: 0 -Log.i("AzureASR", "AudioRecord state: ${audioRecord?.state}, recordingState: ${audioRecord?.recordingState}") -Log.i("AzureASR", "BytesRead = $bytesRead, buffer.isSilent = ${buffer.all { it == 0.toByte() }}") + // 检查初始化状态 + if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) { + throw IllegalStateException("AudioRecord初始化失败") + } - if (!isPaused && bytesRead > 0) { - // 写入Azure流 - audioStream?.write(buffer) + } - // 保存录音 - recordfile?.saveAudioDataToWav(buffer) + // 3. 单独封装线程启动逻辑 + private fun startCaptureThread() { + captureThread = Thread { + // 根据实际采样率计算缓冲区大小 + val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100) + 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) { + + // 读取音频 + val bytesRead = audioRecord?.read(buffer, 0, buffer.size) ?: 0 + + if (!isPaused && bytesRead > 0) { + + // 写入Azure流 + audioStream?.write(buffer) + + // 保存录音 + recordfile?.saveAudioDataToWav(buffer) + } } - } - }.apply { start() } -} + }.apply { start() } + } /** * 停止麦克风捕获并释放所有相关资源 diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt index 06b1d07e9..8cd740589 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt @@ -257,20 +257,24 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin { "setPause" -> { azureAsrHelper.stopAudioPause(); + return } "setcontinue" -> { // azureAsrHelper.stopAudioPause(); azureAsrHelper.setAudiocontinue(); + return } "disableBluetoothAudio" -> { azureAsrHelper.disableBluetoothAudio(); + return } "restoreOriginalAudioState" -> { // azureAsrHelper.stopAudioPause(); azureAsrHelper.restoreOriginalAudioState(); + return } "startContinuousRecognition" -> { @@ -523,19 +527,20 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin { "setAudioOutputDevice" -> { val type = call.argument("type") + var success =false if (type == 0) { // 默认(如果有耳机选耳机,否则使用系统扬声器) - azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.DEFAULT) + success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.DEFAULT) } else if (type == 1) { // 强制使用声器 - azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.SPEAKER) + success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.SPEAKER) } else if (type == 2) { // 强制使用耳机 - azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.HEADPHONES) + success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.HEADPHONES) } - result.success(true) + result.success(success) } else -> { diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt index 2e9559c77..709b99e66 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt @@ -210,9 +210,10 @@ class AzureTtsHelper(private val context: Context) : ITtsService { * * @param device 音频输出设备类型 */ - override fun setAudioOutputDevice(device: AudioOutputDevice) { + override fun setAudioOutputDevice(device: AudioOutputDevice): Boolean{ // Azure TTS使用系统默认的音频路由 // 音频输出设备的控制需要通过Android的AudioManager实现 + try { FileLogger.e(TAG, "音频输出设备类型device=: ${device}") val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager audioManager?.let { manager -> @@ -244,6 +245,11 @@ class AzureTtsHelper(private val context: Context) : ITtsService { } } } + return true + } catch (e: Exception) { + FileLogger.e(TAG, "设置音频输出设备失败: ${e.message}") + return false + } } /** diff --git a/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt b/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt index 9db3c0e93..6493340f7 100644 --- a/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt +++ b/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt @@ -425,10 +425,11 @@ class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope { /** * 设置音频输出设备 */ - override fun setAudioOutputDevice(device: AudioOutputDevice) { + override fun setAudioOutputDevice(device: AudioOutputDevice) : Boolean{ if (useInternalPlayer) { audioPlayer.setAudioOutputDevice(device) } + return true; } /** diff --git a/local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt b/local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt index 00db2f2b3..5b1a07d5a 100644 --- a/local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt +++ b/local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt @@ -103,5 +103,5 @@ interface ITtsService { * * @param device 音频输出设备类型 */ - fun setAudioOutputDevice(device: AudioOutputDevice) + fun setAudioOutputDevice(device: AudioOutputDevice): Boolean } \ No newline at end of file