Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -495,7 +496,7 @@ class WakeWordDetector(
classifierSession.run(Collections.singletonMap(classifierInputName, it)).use { res ->
@Suppress("UNCHECKED_CAST")
val score = (res[0].value as Array<FloatArray>)[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

Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/kangrio/byd/assistant/Constant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) },
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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 ?: "" ) }

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -957,6 +1003,14 @@ private val SENSITIVITY_OPTIONS: List<Pair<String, Float>> = listOf(
"Strict" to 0.7f
)

private val AUDIO_SOURCE_OPTIONS: List<Pair<String, Int>> = 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,6 +50,7 @@ class OpenWakeWordDetector(
log("Starting wake word engine")
val engine = WakeWordDetector(
context = context,
audioSource = audioSource,
modelFile = modelFile,
verifierFile = null,
minScore = sensitivity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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) }
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/com/kangrio/byd/assistant/util/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down