From 75da2bde26fdc60f9dded232b6638fe5f7916a05 Mon Sep 17 00:00:00 2001 From: wolfplus Date: Thu, 13 Mar 2025 19:56:31 +0000 Subject: [PATCH] add --- .../com/example/deep_voice/AzureAsrHelper.kt | 60 +----- .../com/example/deep_voice/MainActivity.kt | 39 +--- .../deep_voice/VoiceInteractionService.kt | 193 +++++++++++++++--- android/app/src/main/res/raw/silence.mp3 | Bin 0 -> 4386 bytes 4 files changed, 166 insertions(+), 126 deletions(-) create mode 100644 android/app/src/main/res/raw/silence.mp3 diff --git a/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt b/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt index ed7af26a9..81bb77844 100644 --- a/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt +++ b/android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt @@ -113,7 +113,7 @@ class AzureAsrHelper { // 获取音频会话 ID 并设置回音消除 audioSessionId = audioRecord?.audioSessionId ?: -1 - setupEchoCancellation() + // setupEchoCancellation() } // 开始一次性语音识别 @@ -538,61 +538,5 @@ class AzureAsrHelper { Log.e(TAG, "释放回音消除资源时出错: ${e.message}", e) } } - - // 检查回音消除是否可用 - fun isEchoCancellationAvailable(): Boolean { - return AcousticEchoCanceler.isAvailable() - } - - // 检查回音消除是否已启用 - fun isEchoCancellationEnabled(): Boolean { - return isEchoCancellationEnabled - } - - // 启用回音消除 - fun enableEchoCancellation(): Boolean { - if (echoCanceler != null) { - try { - echoCanceler?.enabled = true - isEchoCancellationEnabled = echoCanceler?.enabled == true - Log.d(TAG, "回音消除已启用: $isEchoCancellationEnabled") - return isEchoCancellationEnabled - } catch (e: Exception) { - Log.e(TAG, "启用回音消除时出错: ${e.message}", e) - } - } else if (audioSessionId != -1) { - try { - echoCanceler = AcousticEchoCanceler.create(audioSessionId) - if (echoCanceler != null) { - echoCanceler?.enabled = true - isEchoCancellationEnabled = echoCanceler?.enabled == true - Log.d(TAG, "回音消除已创建并启用: $isEchoCancellationEnabled") - return isEchoCancellationEnabled - } - } catch (e: Exception) { - Log.e(TAG, "创建并启用回音消除时出错: ${e.message}", e) - } - } - return false - } - - // 禁用回音消除 - fun disableEchoCancellation(): Boolean { - if (echoCanceler != null) { - try { - echoCanceler?.enabled = false - isEchoCancellationEnabled = echoCanceler?.enabled == true - Log.d(TAG, "回音消除已禁用") - return !isEchoCancellationEnabled - } catch (e: Exception) { - Log.e(TAG, "禁用回音消除时出错: ${e.message}", e) - } - } - return true - } - - // 获取音频会话ID - fun getAudioSessionId(): Int { - return audioSessionId - } + } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt b/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt index c58930736..63835d178 100644 --- a/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt @@ -69,9 +69,9 @@ class MainActivity: FlutterActivity() { // 使用伴生对象存储配置信息,使其在整个应用中可访问 companion object { // Azure 语音服务配置 - var azureSpeechKey: String = "" - var azureSpeechRegion: String = "" - var volcanoAiApiKey: String = "" + var azureSpeechKey: String = "2WDq2KL45QUC5Gk0o8LgQ5bOpnV1kF0ZAEWBTl76AUoG90bbYO4uJQQJ99BCAC3pKaRXJ3w3AAAYACOGzvU1" + var azureSpeechRegion: String = "eastasia" + var volcanoAiApiKey: String = "168deb3d-fd0c-4912-b9f1-aaee5c6743e6" // 标记是否已初始化 var isAzureHelpersInitialized = false @@ -99,8 +99,6 @@ class MainActivity: FlutterActivity() { // 注册广播接收器 registerVoiceInteractionReceiver() - // 自动启动语音交互服务功能已被移除 - Log.d(TAG, "MainActivity 已创建") // 确保 Flutter 引擎准备好后立即设置事件通道 flutterEngine?.let { engine -> @@ -202,36 +200,7 @@ class MainActivity: FlutterActivity() { * 设置方法通道 */ private fun setupMethodChannels(flutterEngine: FlutterEngine) { - // 音频处理通道 - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AUDIO_PROCESSING_CHANNEL).setMethodCallHandler { call, result -> - // 为所有方法返回默认值或成功状态 - when (call.method) { - "isEchoCancellationAvailable" -> { - result.success(azureAsrHelper.isEchoCancellationAvailable()) - } - "isNoiseSuppressionAvailable" -> { - result.success(false) - } - "enableEchoCancellation" -> { - result.success(azureAsrHelper.enableEchoCancellation()) - } - "disableEchoCancellation" -> { - result.success(azureAsrHelper.disableEchoCancellation()) - } - "enableNoiseSuppression" -> { - result.success(true) - } - "disableNoiseSuppression" -> { - result.success(true) - } - "getAudioSessionId" -> { - result.success(azureAsrHelper.getAudioSessionId()) - } - else -> { - result.notImplemented() - } - } - } + // Azure ASR 方法通道 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_ASR_CHANNEL).setMethodCallHandler { call, result -> diff --git a/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt b/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt index d979161d4..293bc8128 100644 --- a/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt +++ b/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt @@ -22,6 +22,9 @@ import android.os.Handler import android.os.Looper import java.util.concurrent.atomic.AtomicBoolean import org.json.JSONArray +import android.media.MediaPlayer +import android.media.AudioAttributes +import android.net.Uri /** * 后台语音交互 Service: @@ -93,6 +96,9 @@ class VoiceInteractionService : Service() { 用户用语音和你交互. """.trimIndent() + // 添加无声媒体播放器 + private var silentPlayer: SilentMediaPlayer? = null + override fun onCreate() { super.onCreate() Log.d(TAG, "onCreate - 语音交互服务正在创建") @@ -109,6 +115,11 @@ class VoiceInteractionService : Service() { initMediaSession() registerMediaButtonReceiver() + + silentPlayer = SilentMediaPlayer(applicationContext) + silentPlayer?.start() + // Log.d(TAG, "无声播放器已启动") + // 设置为媒体播放状态 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) Log.d(TAG, "设置播放状态为PLAYING") @@ -440,53 +451,26 @@ class VoiceInteractionService : Service() { override fun onSessionStopped() { isRecognitionActive = false - // // 如果不是因超时暂停,尝试重新启动 - // if (isActive && !isTimeoutPaused) { - // handler.postDelayed({ - // if (isActive && !isRecognitionActive && !isTtsSpeaking) { - // startVoiceRecognition() - // } - // }, 1000) - // } + } override fun onCanceled(reason: String, errorDetails: String) { isRecognitionActive = false - // // 如果不是因超时暂停,尝试重新启动 - // if (isActive && !isTimeoutPaused) { - // handler.postDelayed({ - // if (isActive && !isRecognitionActive && !isTtsSpeaking) { - // startVoiceRecognition() - // } - // }, 1000) - // } + } override fun onError(error: String) { isRecognitionActive = false playNotification("语音识别出错") - - // 如果不是因超时暂停,尝试重新启动 - // if (isActive && !isTimeoutPaused) { - // handler.postDelayed({ - // if (isActive && !isRecognitionActive && !isTtsSpeaking) { - // startVoiceRecognition() - // } - // }, 2000) - // } + } }) } catch (e: Exception) { isRecognitionActive = false playNotification("启动语音识别失败") - // 延迟重试 - // handler.postDelayed({ - // if (isActive && !isRecognitionActive && !isTtsSpeaking) { - // startVoiceRecognition() - // } - // }, 3000) + } } @@ -588,6 +572,11 @@ class VoiceInteractionService : Service() { */ private fun playNotification(message: String) { isTtsSpeaking = true + // 更新播放状态为播放中,增加接收蓝牙按键事件的几率 + setPlaybackState(PlaybackStateCompat.STATE_PLAYING) + // 确保媒体会话处于活跃状态 + mediaSession.isActive = true + azureTtsHelper.speakText(message, object : AzureTtsHelper.TTSCallback { override fun onSuccess(message: String) { isTtsSpeaking = false @@ -628,10 +617,20 @@ class VoiceInteractionService : Service() { PlaybackStateCompat.ACTION_PAUSE or PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_SKIP_TO_NEXT or - PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or + PlaybackStateCompat.ACTION_STOP or + PlaybackStateCompat.ACTION_SEEK_TO or + PlaybackStateCompat.ACTION_FAST_FORWARD or + PlaybackStateCompat.ACTION_REWIND ) - .setState(state, 0, 1.0f) + // 使用更高的播放速度,让系统认为是真正的播放 + .setState(state, 0, 1.0f, System.currentTimeMillis()) mediaSession.setPlaybackState(playbackStateBuilder.build()) + + // 确保MediaSession激活 + if (!mediaSession.isActive) { + mediaSession.isActive = true + } } /** @@ -786,6 +785,10 @@ class VoiceInteractionService : Service() { override fun onDestroy() { Log.d(TAG, "onDestroy - 语音交互服务正在销毁") + // 停止无声播放器 + silentPlayer?.release() + silentPlayer = null + // 停止监控 stopMonitoring() @@ -901,4 +904,128 @@ class VoiceInteractionService : Service() { Log.e(TAG, "测试媒体按钮事件处理失败: ${e.message}", e) } } + + /** + * 无声媒体播放器类 + * 用于播放一个1秒的静音音频,确保系统认为app在实际播放声音 + */ + inner class SilentMediaPlayer(private val context: Context) { + private var mediaPlayer: MediaPlayer? = null + private var isPlayingAudio = false + + init { + try { + setupMediaPlayer() + } catch (e: Exception) { + Log.e(TAG, "初始化无声播放器失败: ${e.message}", e) + } + } + + private fun setupMediaPlayer() { + try { + // 创建一个MediaPlayer + mediaPlayer = MediaPlayer() + + // 设置一个自带的静音资源 + val afd = context.resources.openRawResourceFd(R.raw.silence) + if (afd != null) { + mediaPlayer?.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length) + afd.close() + } else { + // 如果没有资源,尝试使用一个虚拟数据源 + Log.w(TAG, "未找到静音资源,尝试使用虚拟数据源") + mediaPlayer?.setDataSource(context, Uri.parse("android.resource://${context.packageName}/raw/silence")) + } + + // 设置循环播放 + mediaPlayer?.isLooping = false + + // 设置音量为0 + mediaPlayer?.setVolume(0f, 0f) + + // 设置音频特性 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val audioAttributes = AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build() + mediaPlayer?.setAudioAttributes(audioAttributes) + } else { + @Suppress("DEPRECATION") + mediaPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC) + } + + // 准备播放 + mediaPlayer?.prepare() + + // 设置完成监听器 - 虽然设置了循环播放,但以防万一 + mediaPlayer?.setOnCompletionListener { + Log.d(TAG, "无声播放完成") + isPlayingAudio = false + // start() + } + + // 设置错误监听器 + mediaPlayer?.setOnErrorListener { _, what, extra -> + Log.e(TAG, "无声播放错误: what=$what, extra=$extra") + isPlayingAudio = false + // 尝试重新创建 + setupMediaPlayer() + true + } + + Log.d(TAG, "无声播放器初始化成功") + } catch (e: Exception) { + Log.e(TAG, "设置无声播放器失败: ${e.message}", e) + mediaPlayer?.release() + mediaPlayer = null + } + } + + fun start() { + try { + if (mediaPlayer == null) { + setupMediaPlayer() + } + + if (mediaPlayer != null && !isPlayingAudio) { + mediaPlayer?.start() + isPlayingAudio = true + Log.d(TAG, "无声播放开始") + } + } catch (e: Exception) { + Log.e(TAG, "开始无声播放失败: ${e.message}", e) + + } + } + + fun stop() { + try { + if (mediaPlayer != null && isPlayingAudio) { + mediaPlayer?.pause() + isPlayingAudio = false + Log.d(TAG, "无声播放停止") + } + } catch (e: Exception) { + Log.e(TAG, "停止无声播放失败: ${e.message}", e) + } + } + + fun release() { + try { + mediaPlayer?.stop() + mediaPlayer?.release() + mediaPlayer = null + isPlayingAudio = false + Log.d(TAG, "无声播放器已释放") + } catch (e: Exception) { + Log.e(TAG, "释放无声播放器失败: ${e.message}", e) + } + } + + fun isPlaying(): Boolean { + return isPlayingAudio && mediaPlayer != null + } + } + } \ No newline at end of file diff --git a/android/app/src/main/res/raw/silence.mp3 b/android/app/src/main/res/raw/silence.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f5bd92f88d17566216025580c48a5e26cdc2067b GIT binary patch literal 4386 zcmeZtF=k-^0i}@OU{@f`$H2hslUSB!W~gVbXJ}vmmV^-he>)sN;zF37d1?7T7C#Vc zFfa&wV-OG!6O)ydRaVy2)YR8EH8r)ic64;~@bL5V3kiveib_aGOH0eiDJm+etgNrE zZ*A@F?w&Yt`t<2@=Pp^YWYww-8#Zj)wr9_tLx)bBIC1XW)vH%;-+uV;;q&Kj-@g6) z`RC7{|Nnz*2D&jB=te_J5C;ZSSZg3mfp3~W@|S_x|9=Y{VPHPMz_(^b literal 0 HcmV?d00001