diff --git a/app/src/main/java/dev/kaizen/app/live/AudioPipeline.kt b/app/src/main/java/dev/kaizen/app/live/AudioPipeline.kt index 064b276..a43cf48 100644 --- a/app/src/main/java/dev/kaizen/app/live/AudioPipeline.kt +++ b/app/src/main/java/dev/kaizen/app/live/AudioPipeline.kt @@ -5,16 +5,17 @@ import android.content.Context import android.content.pm.PackageManager import android.media.AudioAttributes import android.media.AudioFormat +import android.media.AudioManager import android.media.AudioRecord import android.media.AudioTrack import android.media.MediaRecorder +import android.media.audiofx.AcousticEchoCanceler import android.util.Base64 import android.util.Log import androidx.core.content.ContextCompat import kotlinx.coroutines.* import java.nio.ByteBuffer import java.nio.ByteOrder -import kotlin.math.abs import kotlin.math.sqrt /** @@ -51,8 +52,11 @@ class AudioPipeline(private val context: Context) { var onChunk: ((base64Pcm: String) -> Unit)? = null var onAmplitude: ((Float) -> Unit)? = null + private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + private var previousAudioMode = AudioManager.MODE_NORMAL private var recorder: AudioRecord? = null private var track: AudioTrack? = null + private var aec: AcousticEchoCanceler? = null private var captureJob: Job? = null @Volatile private var ducking = false @@ -87,6 +91,24 @@ class AudioPipeline(private val context: Context) { } recorder = rec + + previousAudioMode = audioManager.mode + audioManager.mode = AudioManager.MODE_IN_COMMUNICATION + @Suppress("DEPRECATION") + audioManager.isSpeakerphoneOn = true + + if (AcousticEchoCanceler.isAvailable()) { + try { + aec = AcousticEchoCanceler.create(rec.audioSessionId) + aec?.enabled = true + Log.i(TAG, "AEC enabled (session=${rec.audioSessionId})") + } catch (e: Exception) { + Log.w(TAG, "AEC init failed: ${e.message}") + } + } else { + Log.w(TAG, "AEC not available on this device") + } + rec.startRecording() capturing = true @@ -121,8 +143,11 @@ class AudioPipeline(private val context: Context) { try { recorder?.stop() } catch (_: IllegalStateException) {} + aec?.release() + aec = null recorder?.release() recorder = null + audioManager.mode = previousAudioMode } // ─── Playback ───────────────────────────────────────────────────────── @@ -137,7 +162,7 @@ class AudioPipeline(private val context: Context) { val t = AudioTrack.Builder() .setAudioAttributes( AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_ASSISTANT) + .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .build() )