diff --git a/CHANGELOG b/CHANGELOG index 13f2ba3..4dba64f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +- 1.0.4 + • add configurable audio source for wake-word detection + • improve ADB discovery and shell execution timeouts for better reliability + - 1.0.3 • Migrate wake-word detection engine from Snowboy to AndroidWakeWord to reduce false triggers • Recommended to clear app data after updated to this version diff --git a/androidwakeword/src/main/java/com/suxsem/androidwakeword/WakeWordDetector.kt b/androidwakeword/src/main/java/com/suxsem/androidwakeword/WakeWordDetector.kt index b561399..712394b 100644 --- a/androidwakeword/src/main/java/com/suxsem/androidwakeword/WakeWordDetector.kt +++ b/androidwakeword/src/main/java/com/suxsem/androidwakeword/WakeWordDetector.kt @@ -29,6 +29,7 @@ private enum class VadMode { class WakeWordDetector( private val context: Context, + private val audioSource: Int, private val modelFile: String, private val verifierFile: String?, private val minScore: Float, @@ -70,7 +71,7 @@ class WakeWordDetector( @SuppressLint("MissingPermission") private val audioRecord = AudioRecord( - MediaRecorder.AudioSource.DEFAULT, + audioSource, sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, @@ -495,7 +496,7 @@ class WakeWordDetector( classifierSession.run(Collections.singletonMap(classifierInputName, it)).use { res -> @Suppress("UNCHECKED_CAST") val score = (res[0].value as Array)[0][0] - Log.d("WakeWordDetector", "Classifier score: ${"%.6f".format(score)}:${"%.6f".format(minScore)}") + Log.d("WakeWordDetector", "Classifier score: ${"%.6f".format(score)} of ${"%.6f".format(minScore)}") if (score < minScore) return diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d445abc..b3f758b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,8 +8,8 @@ plugins { alias(libs.plugins.kotlin.compose) } -val appVersionCode = 4 -val appVersionName = "1.0.3" +val appVersionCode = 5 +val appVersionName = "1.0.4" val betaTags = providers.provider { providers.exec { diff --git a/app/src/main/java/com/kangrio/byd/assistant/Constant.kt b/app/src/main/java/com/kangrio/byd/assistant/Constant.kt index 9aa448a..15e29f9 100644 --- a/app/src/main/java/com/kangrio/byd/assistant/Constant.kt +++ b/app/src/main/java/com/kangrio/byd/assistant/Constant.kt @@ -7,6 +7,7 @@ object Constant { const val PREFS_START_HOTWORD = "PREFS_START_HOTWORD" const val PREFS_HOTWORD_MODEL_NAME = "PREFS_HOTWORD_MODEL_NAME" const val PREFS_HOTWORD_SENSITIVITY = "PREFS_HOTWORD_SENSITIVITY" + const val PREFS_MIC_AUDIO_SOURCE = "PREFS_MIC_AUDIO_SOURCE" const val PREFS_PLAY_DING_ON_START = "PREFS_PLAY_DING_ON_START" const val PREFS_ASSISTANT_PACKAGE_COMPONENT = "PREFS_ASSISTANT_PACKAGE_COMPONENT" const val PREFS_LAST_OTA_CHECK_TIME = "PREFS_LAST_OTA_CHECK_TIME" diff --git a/app/src/main/java/com/kangrio/byd/assistant/activity/SettingsActivity.kt b/app/src/main/java/com/kangrio/byd/assistant/activity/SettingsActivity.kt index b4e5790..a21f24b 100644 --- a/app/src/main/java/com/kangrio/byd/assistant/activity/SettingsActivity.kt +++ b/app/src/main/java/com/kangrio/byd/assistant/activity/SettingsActivity.kt @@ -3,6 +3,7 @@ package com.kangrio.byd.assistant.activity import android.Manifest import android.content.Intent import android.content.pm.PackageManager +import android.media.MediaRecorder import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity @@ -124,6 +125,7 @@ class SettingsActivity : ComponentActivity() { initialOtaRelease = otaRelease, onStateToggle = { state -> onStateToggle(state) }, onPlayDingToggle = { state -> Preferences.playDingOnStart = state }, + onAudioSourceChange = { source -> VoiceWakeService.setAudioSource(this, source) }, onSensitivityChange = { VoiceWakeService.setSensitivity(this, it) }, onModelChanged = { model -> VoiceWakeService.setModel(this, model) }, onModelDelete = { model -> onModelDelete(model) }, @@ -186,6 +188,7 @@ fun SettingsScreen( initialOtaRelease: ReleaseInfo? = null, onStateToggle: (Boolean) -> Unit, onPlayDingToggle: (Boolean) -> Unit = {}, + onAudioSourceChange: (Int) -> Unit = {}, onSensitivityChange: (Float) -> Unit = {}, onModelChanged: (String) -> Unit = { _-> }, onModelDelete: (String) -> Boolean = { _ -> false }, @@ -237,6 +240,9 @@ fun SettingsScreen( } } + var expandedAudioSource by remember { mutableStateOf(false) } + var selectedAudioSource by remember { mutableStateOf(AUDIO_SOURCE_OPTIONS.firstOrNull { (_, s) -> s == Preferences.micAudioSource }?.first ?: "Default" ) } + var expandedSensitivity by remember { mutableStateOf(false) } var selectedSensitivityLevel by remember { mutableStateOf(SENSITIVITY_OPTIONS.firstOrNull { (_, s) -> s == Preferences.hotwordSensitivity }?.first ?: "" ) } @@ -358,6 +364,46 @@ fun SettingsScreen( ) } + SettingRow( + enabled = isStateOn, + label = "Audio Source", + description = "Select the audio source to use for wake-word detection." + ) { + Box { + FilledTonalButton( + onClick = { expandedAudioSource = !expandedAudioSource } + ) { + Text(selectedAudioSource) + } + + DropdownMenu( + expanded = expandedAudioSource, + onDismissRequest = { expandedAudioSource = false }, + offset = DpOffset( + x = 0.dp, + y = 56.dp + ) + ) { + Column( + modifier = Modifier + .heightIn(max = 300.dp) + .verticalScroll(rememberScrollState()) + ) { + AUDIO_SOURCE_OPTIONS.forEach { (label, source) -> + DropdownMenuItem( + text = { Text(label) }, + onClick = { + onAudioSourceChange(source) + selectedAudioSource = label + expandedAudioSource = false + } + ) + } + } + } + } + } + SettingRow( enabled = isStateOn, label = "Wake Word", @@ -957,6 +1003,14 @@ private val SENSITIVITY_OPTIONS: List> = listOf( "Strict" to 0.7f ) +private val AUDIO_SOURCE_OPTIONS: List> = listOf( + "Default" to MediaRecorder.AudioSource.DEFAULT, + "Mic" to MediaRecorder.AudioSource.MIC, + "CamCorder" to MediaRecorder.AudioSource.CAMCORDER, + "Recognize" to MediaRecorder.AudioSource.VOICE_RECOGNITION, + "Unprocessed" to MediaRecorder.AudioSource.UNPROCESSED, +) + @Preview(showBackground = true, widthDp = 480, heightDp = 480) @Composable private fun SettingsScreenPreview() { diff --git a/app/src/main/java/com/kangrio/byd/assistant/service/OpenWakeWordDetector.kt b/app/src/main/java/com/kangrio/byd/assistant/service/OpenWakeWordDetector.kt index c537387..aeae596 100644 --- a/app/src/main/java/com/kangrio/byd/assistant/service/OpenWakeWordDetector.kt +++ b/app/src/main/java/com/kangrio/byd/assistant/service/OpenWakeWordDetector.kt @@ -12,6 +12,7 @@ import java.io.File class OpenWakeWordDetector( private val context: Context, + private val audioSource: Int = android.media.MediaRecorder.AudioSource.DEFAULT, private val modelName: String = "hey_billy", private val sensitivity: Float = 0.5f, private val onDetected: () -> Unit @@ -49,6 +50,7 @@ class OpenWakeWordDetector( log("Starting wake word engine") val engine = WakeWordDetector( context = context, + audioSource = audioSource, modelFile = modelFile, verifierFile = null, minScore = sensitivity, diff --git a/app/src/main/java/com/kangrio/byd/assistant/service/VoiceWakeService.kt b/app/src/main/java/com/kangrio/byd/assistant/service/VoiceWakeService.kt index 1f62eb9..771cc2d 100644 --- a/app/src/main/java/com/kangrio/byd/assistant/service/VoiceWakeService.kt +++ b/app/src/main/java/com/kangrio/byd/assistant/service/VoiceWakeService.kt @@ -81,6 +81,11 @@ class VoiceWakeService : Service() { val sensitivity = intent.extras?.getFloat("sensitivity") ?: return START_STICKY setSensitivity(sensitivity) } + + SET_AUDIO_SOURCE -> { + val source = intent.extras?.getInt("source") ?: return START_STICKY + setAudioSource(source) + } } return START_STICKY @@ -103,6 +108,13 @@ class VoiceWakeService : Service() { } } + fun setAudioSource(source: Int) { + scope.launch { + Preferences.micAudioSource = source + restartHotwordDetection() + } + } + fun setModel(modelName: String) { scope.launch { Preferences.hotwordModelName = modelName @@ -118,9 +130,11 @@ class VoiceWakeService : Service() { if (!Utils.setupCompleted(this@VoiceWakeService) || !Preferences.startHotword) return val modelName = Preferences.hotwordModelName + val audioSource = Preferences.micAudioSource detector?.stop() detector = OpenWakeWordDetector( context = this@VoiceWakeService, + audioSource = audioSource, modelName = modelName, sensitivity = Preferences.hotwordSensitivity, ) { @@ -234,6 +248,7 @@ class VoiceWakeService : Service() { const val STOP_HOTWORD_DETECTION = "com.kangrio.byd.assistant.action.STOP_HOTWORD_DETECTION" const val SET_MODEL = "com.kangrio.byd.assistant.action.SET_MODEL" const val SET_SENSITIVITY = "com.kangrio.byd.assistant.action.SET_SENSITIVITY" + const val SET_AUDIO_SOURCE = "com.kangrio.byd.assistant.action.SET_AUDIO_SOURCE" fun startService(context: Context) { if (isStarted || !Utils.setupCompleted(context)) return @@ -269,5 +284,13 @@ class VoiceWakeService : Service() { } context.startService(intent) } + + fun setAudioSource(context: Context, source: Int) { + val intent = Intent(context, VoiceWakeService::class.java).apply { + action = SET_AUDIO_SOURCE + putExtra("source", source) + } + context.startService(intent) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/kangrio/byd/assistant/util/Preferences.kt b/app/src/main/java/com/kangrio/byd/assistant/util/Preferences.kt index 3aa5144..37cdca1 100644 --- a/app/src/main/java/com/kangrio/byd/assistant/util/Preferences.kt +++ b/app/src/main/java/com/kangrio/byd/assistant/util/Preferences.kt @@ -2,6 +2,7 @@ package com.kangrio.byd.assistant.util import android.content.Context import android.content.SharedPreferences +import android.media.MediaRecorder import com.kangrio.byd.assistant.Constant import androidx.core.content.edit @@ -27,6 +28,10 @@ object Preferences { get() = prefs.getFloat(Constant.PREFS_HOTWORD_SENSITIVITY, 0.5f) set(value) = prefs.edit { putFloat(Constant.PREFS_HOTWORD_SENSITIVITY, value) } + var micAudioSource: Int + get() = prefs.getInt(Constant.PREFS_MIC_AUDIO_SOURCE, MediaRecorder.AudioSource.DEFAULT) + set(value) = prefs.edit { putInt(Constant.PREFS_MIC_AUDIO_SOURCE, value) } + var playDingOnStart: Boolean get() = prefs.getBoolean(Constant.PREFS_PLAY_DING_ON_START, true) set(value) = prefs.edit { putBoolean(Constant.PREFS_PLAY_DING_ON_START, value) } diff --git a/app/src/main/java/com/kangrio/byd/assistant/util/Utils.kt b/app/src/main/java/com/kangrio/byd/assistant/util/Utils.kt index 208e4fc..b0e8e7c 100644 --- a/app/src/main/java/com/kangrio/byd/assistant/util/Utils.kt +++ b/app/src/main/java/com/kangrio/byd/assistant/util/Utils.kt @@ -235,14 +235,14 @@ object Utils { suspend fun adbShell(context: Context, cmd: String) = withContext(Dispatchers.IO) { setupUserHome(context) - val result = withTimeoutOrNull(10_000L.milliseconds) { + val result = withTimeoutOrNull(20_000L.milliseconds) { while (isActive) { try { - var dAdb = Dadb.discover(connectTimeout = 1000, socketTimeout = 1000) + var dAdb = Dadb.discover(connectTimeout = 2000, socketTimeout = 5000) dAdb?.shell("echo 'init'") ?: throw Throwable("fail to connect adb") dAdb.close() - dAdb = Dadb.discover(connectTimeout = 1000, socketTimeout = 5000) + dAdb = Dadb.discover(connectTimeout = 2000, socketTimeout = 5000) dAdb?.shell(cmd) dAdb?.close() return@withTimeoutOrNull true