From c5824c9439947f4dc3acc76e167b9e07ca3a8fd7 Mon Sep 17 00:00:00 2001 From: isp Date: Fri, 13 Sep 2024 22:53:43 -0600 Subject: [PATCH] =?UTF-8?q?Bump=CB=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/codeStyles/codeStyleConfig.xml | 1 - .../com/birdsounds/identify/MainActivity.kt | 14 +++-- .../identify/MessageListenerService.kt | 14 +++-- .../birdsounds/identify/SoundClassifier.kt | 12 ++-- wear/src/main/AndroidManifest.xml | 2 +- .../identify/MessageListenerService.kt | 35 ----------- .../identify/presentation/MainActivity.kt | 45 +++++--------- .../identify/presentation/MainState.kt | 8 +-- .../presentation/MessageListenerService.kt | 28 +++++++++ .../identify/presentation/MessageSender.kt | 5 +- .../identify/presentation/SoundRecorder.kt | 54 ++++++++++------ .../identify/presentation/SpeciesList.kt | 62 +++++++++++++++++++ wear/src/main/res/layout/layout.xml | 6 ++ 13 files changed, 182 insertions(+), 104 deletions(-) delete mode 100644 wear/src/main/java/com/birdsounds/identify/MessageListenerService.kt create mode 100644 wear/src/main/java/com/birdsounds/identify/presentation/MessageListenerService.kt create mode 100644 wear/src/main/java/com/birdsounds/identify/presentation/SpeciesList.kt create mode 100644 wear/src/main/res/layout/layout.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index 6e6eec1..a55e7a1 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,6 +1,5 @@ - \ No newline at end of file diff --git a/mobile/src/main/java/com/birdsounds/identify/MainActivity.kt b/mobile/src/main/java/com/birdsounds/identify/MainActivity.kt index a38311c..73dabd4 100644 --- a/mobile/src/main/java/com/birdsounds/identify/MainActivity.kt +++ b/mobile/src/main/java/com/birdsounds/identify/MainActivity.kt @@ -15,6 +15,8 @@ import com.google.android.gms.wearable.Wearable class MainActivity : AppCompatActivity() { // private lateinit var soundClassifier: SoundClassifier + val REQUEST_PERMISSIONS = 1337 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -28,20 +30,22 @@ class MainActivity : AppCompatActivity() { } } ) - Downloader.downloadModels(this); - requestPermissions(); + Downloader.downloadModels(this) + requestPermissions() soundClassifier = SoundClassifier(this, SoundClassifier.Options()) Location.requestLocation(this, soundClassifier) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets + } } companion object { - const val REQUEST_PERMISSIONS = 1337 - @SuppressLint("StaticFieldLeak") - lateinit var soundClassifier: SoundClassifier + var soundClassifier: SoundClassifier? = null +// fun getSoundClassifier(): SoundClassifier? { +// return soundClassifier +// } } private fun requestPermissions() { diff --git a/mobile/src/main/java/com/birdsounds/identify/MessageListenerService.kt b/mobile/src/main/java/com/birdsounds/identify/MessageListenerService.kt index 1509e0c..431ecbe 100644 --- a/mobile/src/main/java/com/birdsounds/identify/MessageListenerService.kt +++ b/mobile/src/main/java/com/birdsounds/identify/MessageListenerService.kt @@ -18,16 +18,18 @@ class MessageListenerService : WearableListenerService() { override fun onMessageReceived(p0: MessageEvent) { super.onMessageReceived(p0) +// MainActivity val soundclassifier = MainActivity.soundClassifier if (soundclassifier == null) { Log.w(tag, "Have invalid sound classifier") + return } else { Log.w(tag, "Have valid classifier") } - val short_array = ShortArray(48000 * 3); + val short_array = ShortArray(48000 * 3) var tstamp_bytes = p0.data.copyOfRange(0, Long.SIZE_BYTES) var audio_bytes = p0.data.copyOfRange(Long.SIZE_BYTES, p0.data.size) - + var string_send: String = "" ByteBuffer.wrap(audio_bytes).order( ByteOrder.LITTLE_ENDIAN @@ -39,9 +41,13 @@ class MessageListenerService : WearableListenerService() { val score = sorted_list[i].value val index = sorted_list[i].index val species_name = soundclassifier.labelList[index] - Log.w(tag, species_name + ", " + score.toString()); + Log.w(tag, species_name + ", " + score.toString()) + string_send+= species_name + string_send+=',' + string_send+=score.toString() + string_send+=';' } - MessageSenderFromPhone.sendMessage("/audio", tstamp_bytes, this) + MessageSenderFromPhone.sendMessage("/audio", tstamp_bytes + string_send.toByteArray(), this) // Log.i(tag , short_array.map( { abs(it)}).sum().toString()) // Log.i(tag, short_array[0].toString()) // Log.i(tag, p0.data.toString(Charsets.US_ASCII)) diff --git a/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt b/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt index 89993de..c6f1f2c 100644 --- a/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt +++ b/mobile/src/main/java/com/birdsounds/identify/SoundClassifier.kt @@ -5,6 +5,7 @@ import android.location.Location import android.os.SystemClock import android.preference.PreferenceManager import android.util.Log +import androidx.annotation.Nullable import org.tensorflow.lite.Interpreter import java.io.BufferedReader import java.io.File @@ -26,6 +27,7 @@ import java.nio.ShortBuffer import kotlin.math.round import kotlin.math.sin + class SoundClassifier( context: Context, private val options: Options = Options() @@ -60,10 +62,10 @@ class SoundClassifier( /** Names of the model's output classes. */ - public lateinit var labelList: List + lateinit var labelList: List /** Names of the model's output classes. */ - public lateinit var assetList: List + lateinit var assetList: List /** How many milliseconds between consecutive model inference calls. */ private var inferenceInterval = 800L @@ -315,10 +317,10 @@ class SoundClassifier( private const val NANOS_IN_MILLIS = 1_000_000.toDouble() } - public fun executeScoring( + fun executeScoring( short_array: ShortArray ): List> { - val highPass = 0; + val highPass = 0 val butterworth = Butterworth() butterworth.highPass(6, 48000.0, highPass.toDouble()) @@ -372,7 +374,7 @@ class SoundClassifier( // Load new audio samples // val sampleCounts = loadAudio(recordingBuffer) - val sampleCounts = 0; + val sampleCounts = 0 if (sampleCounts == 0) { return@task } diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index dc1f106..8f403bd 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -37,7 +37,7 @@ diff --git a/wear/src/main/java/com/birdsounds/identify/MessageListenerService.kt b/wear/src/main/java/com/birdsounds/identify/MessageListenerService.kt deleted file mode 100644 index a22871f..0000000 --- a/wear/src/main/java/com/birdsounds/identify/MessageListenerService.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.birdsounds.identify - -import android.app.Service - - - -import android.content.Intent -import android.util.Half.abs -import android.util.Log -import androidx.localbroadcastmanager.content.LocalBroadcastManager -import com.birdsounds.identify.presentation.MessageSender -import com.google.android.gms.wearable.MessageEvent -import com.google.android.gms.wearable.WearableListenerService -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.nio.ShortBuffer - -class MessageListenerService : WearableListenerService() { - private val tag = "MessageListenerService" - - // fun placeSoundClassifier(soundClassifier: SoundClassifier) - override fun onMessageReceived(p0: MessageEvent) { - super.onMessageReceived(p0) - - - val t_scored = ByteBuffer.wrap(p0.data).getLong() - MessageSender.messageLog.add(t_scored) - Log.w("MSG_RECV", t_scored.toString()) -// Log.i(tag , short_array.map( { abs(it)}).sum().toString()) -// Log.i(tag, short_array[0].toString()) -// Log.i(tag, p0.data.toString(Charsets.US_ASCII)) -// broadcastMessage(p0) - } - -} \ No newline at end of file diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/MainActivity.kt b/wear/src/main/java/com/birdsounds/identify/presentation/MainActivity.kt index f0b3b6e..71f40af 100644 --- a/wear/src/main/java/com/birdsounds/identify/presentation/MainActivity.kt +++ b/wear/src/main/java/com/birdsounds/identify/presentation/MainActivity.kt @@ -16,13 +16,11 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts.RequestPermission import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.viewmodel.compose.viewModel import androidx.wear.compose.navigation.SwipeDismissableNavHost import androidx.wear.compose.navigation.composable @@ -30,11 +28,8 @@ import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import com.birdsounds.identify.presentation.theme.IdentifyTheme import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.audio.ui.VolumeViewModel -import com.google.android.horologist.compose.layout.AppScaffold import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* val flow_stream = MutableStateFlow("") @@ -63,17 +58,11 @@ class MainActivity : ComponentActivity() { @OptIn(ExperimentalHorologistApi::class) @Composable +@Preview fun WearApp() { IdentifyTheme { lateinit var requestPermissionLauncher: ManagedActivityResultLauncher - -// val job = launch { -// flow_stream.collect { -// print("$it ") -// } -// } - val context = LocalContext.current val activity = context.findActivity() val scope = rememberCoroutineScope() @@ -99,20 +88,20 @@ fun WearApp() { val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current - AppScaffold { - DisposableEffect(mainState, scope, lifecycleOwner) { - val lifecycleObserver = object : DefaultLifecycleObserver { - override fun onStop(owner: LifecycleOwner) { - super.onStop(owner) - } - } - lifecycleOwner.lifecycle.addObserver(lifecycleObserver) - - onDispose { - lifecycleOwner.lifecycle.removeObserver(lifecycleObserver) - } - } - SwipeDismissableNavHost(navController = navController, startDestination = "speaker") { +// AppScaffold { +// DisposableEffect(mainState, scope, lifecycleOwner) { +// val lifecycleObserver = object : DefaultLifecycleObserver { +// override fun onStop(owner: LifecycleOwner) { +// super.onStop(owner) +// } +// } +// lifecycleOwner.lifecycle.addObserver(lifecycleObserver) +// +// onDispose { +// lifecycleOwner.lifecycle.removeObserver(lifecycleObserver) +// } +// } + var sdnv = SwipeDismissableNavHost(navController = navController, startDestination = "speaker") { composable("speaker") { StartRecordingScreen( context = context, @@ -125,7 +114,7 @@ fun WearApp() { }, ) } - } +// } } } } diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/MainState.kt b/wear/src/main/java/com/birdsounds/identify/presentation/MainState.kt index e265d24..135d40d 100644 --- a/wear/src/main/java/com/birdsounds/identify/presentation/MainState.kt +++ b/wear/src/main/java/com/birdsounds/identify/presentation/MainState.kt @@ -46,7 +46,7 @@ class MainState(private val activity: Activity, private val requestPermission: ( when (appState) { is AppState.Ready -> when { (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) -> { - Log.e(TAG, "Permissions granted, continuing to record"); + Log.e(TAG, "Permissions granted, continuing to record") appState = AppState.Recording @@ -54,7 +54,7 @@ class MainState(private val activity: Activity, private val requestPermission: ( } else -> { - Log.e(TAG, "Requesting permissions"); + Log.e(TAG, "Requesting permissions") requestPermission() } } @@ -100,7 +100,7 @@ private suspend fun record(activity: (Activity),soundRecorder: SoundRecorder, coroutineScope { // Kick off a parallel job to - Log.e(TAG, "Start recording"); // + Log.e(TAG, "Start recording") // val recordingJob = launch { soundRecorder.record() } // val recordingJob = launch { soundRecorder.record() } // SoundRecorder.record(); @@ -142,7 +142,7 @@ private suspend fun record(activity: (Activity),soundRecorder: SoundRecorder, } -object channelCallback : ChannelClient.ChannelCallback() { +object channelCallback : ChannelCallback() { override fun onChannelOpened(channel: ChannelClient.Channel) { super.onChannelOpened(channel) Log.e(TAG,"Opened channel")} diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/MessageListenerService.kt b/wear/src/main/java/com/birdsounds/identify/presentation/MessageListenerService.kt new file mode 100644 index 0000000..e585b92 --- /dev/null +++ b/wear/src/main/java/com/birdsounds/identify/presentation/MessageListenerService.kt @@ -0,0 +1,28 @@ +package com.birdsounds.identify.presentation + +import com.google.android.gms.wearable.MessageEvent +import com.google.android.gms.wearable.WearableListenerService +import java.nio.ByteBuffer + + +class MessageListenerService : WearableListenerService() { + private val tag = "MessageListenerService" + + override fun onMessageReceived(p0: MessageEvent) { + super.onMessageReceived(p0) + val t_scored = ByteBuffer.wrap(p0.data).getLong() + var byte_strings: ByteArray = p0.data.copyOfRange(8, p0.data.size) + var score_species_string = byte_strings.decodeToString() + var list_strings: List = score_species_string.split(';') + list_strings.map({ + var split_str = it.split(',') + if (split_str.size == 2) { + var out = AScore(split_str[0], split_str[1].toFloat(), t_scored) + if (out.score > 0.05) { + SpeciesList.add_observation(out) + } + } + }) + MessageSender.messageLog.add(t_scored) + } +} \ No newline at end of file diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/MessageSender.kt b/wear/src/main/java/com/birdsounds/identify/presentation/MessageSender.kt index 69a209d..98025b3 100644 --- a/wear/src/main/java/com/birdsounds/identify/presentation/MessageSender.kt +++ b/wear/src/main/java/com/birdsounds/identify/presentation/MessageSender.kt @@ -3,10 +3,8 @@ package com.birdsounds.identify.presentation import android.content.Context import android.util.Log -import co.touchlab.stately.collections.ConcurrentMutableCollection -import co.touchlab.stately.collections.ConcurrentMutableList import co.touchlab.stately.collections.ConcurrentMutableSet -import com.google.android.gms.tasks.Tasks +import com.google.android.gms.tasks.Tasksx import com.google.android.gms.wearable.Wearable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -14,6 +12,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import java.util.concurrent.ExecutionException + object MessageSender { const val tag = "MessageSender" private val job = Job() diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/SoundRecorder.kt b/wear/src/main/java/com/birdsounds/identify/presentation/SoundRecorder.kt index 1ae067a..2ed0702 100644 --- a/wear/src/main/java/com/birdsounds/identify/presentation/SoundRecorder.kt +++ b/wear/src/main/java/com/birdsounds/identify/presentation/SoundRecorder.kt @@ -6,10 +6,8 @@ import android.content.Context import android.media.AudioFormat import android.media.AudioRecord import android.media.MediaRecorder -import android.os.Message import android.util.Log import androidx.annotation.RequiresPermission -import co.touchlab.stately.collections.ConcurrentMutableCollection import kotlinx.coroutines.suspendCancellableCoroutine import java.nio.ByteBuffer import java.time.Instant @@ -46,7 +44,7 @@ class SoundRecorder( // Log.w(TAG, buffer_size.toString()) val bufferSizeInBytes = - sampleRateInHz * 3 * 2; // 3 second sample, 2 bytes for each sample + sampleRateInHz * 3 * 2 // 3 second sample, 2 bytes for each sample val chunk_size = 2 * sampleRateInHz / 4 // 250ms segments, 2 bytes for each sample val num_chunks: Int = bufferSizeInBytes / chunk_size @@ -62,25 +60,44 @@ class SoundRecorder( ) audioRecord.startRecording() + val thread = Thread { - var num_interactions = 0 +// var sent_first: Boolean = false + var ignore_warmup: Boolean = true + + var num_chunked_since_last_send = 0 var last_tstamp: Long = Instant.now().toEpochMilli() + var do_send_message: Boolean = false while (true) { if (Thread.interrupted()) { // check for the interrupted flag, reset it, and throw exception - Log.w(TAG, "Finished thread"); - break; + Log.w(TAG, "Finished thread") + break } chunk_index = chunk_index.mod(num_chunks) val out = audioRecord.read( /* audioData = */ chunked_audio_bytes[chunk_index], /* offsetInBytes = */ 0, /* sizeInBytes = */ chunk_size, - /* readMode = */ android.media.AudioRecord.READ_BLOCKING + /* readMode = */ AudioRecord.READ_BLOCKING ) + num_chunked_since_last_send += 1 - chunk_index += 1; - if ((last_tstamp in MessageSender.messageLog) || (num_interactions == 0)) { + do_send_message = false + if (num_chunked_since_last_send >= num_chunks) { + do_send_message = true + Log.w("MSG","sending message because full 3s have passed") + } else if ((last_tstamp in MessageSender.messageLog) && (num_chunked_since_last_send>4)) { + do_send_message = true + Log.w("MSG","Send message because the phone has finished") + } else if ((ignore_warmup) && (num_chunked_since_last_send > 2)) { + do_send_message = true + Log.w("MSG","Sent message because ignoring warmup") + } + + + chunk_index += 1 + if ((do_send_message)) { var tstamp: Long = Instant.now().toEpochMilli() val tstamp_buffer = ByteBuffer.allocate(Long.SIZE_BYTES) val tstamp_bytes = tstamp_buffer.putLong(tstamp).array() @@ -89,24 +106,25 @@ class SoundRecorder( for (i in 0..(num_chunks - 1)) { var c_index = i + chunk_index c_index = c_index.mod(num_chunks) - strr += c_index.toString(); + strr += c_index.toString() strr += ' ' byte_send += chunked_audio_bytes[c_index] } -// Log.w(TAG, strr) -// Log.w("MSG_SENT",byte_send.sum().toString()+",. "+ tstamp.toString()) +// do_send_message = false; + num_chunked_since_last_send = 0 + MessageSender.messageLog.clear() MessageSender.sendMessage("/audio", tstamp_bytes + byte_send, context) - last_tstamp = tstamp; - } + last_tstamp = tstamp + } } - }; - thread.start(); + } + thread.start() // thread.join(); cont.invokeOnCancellation { - thread.interrupt(); - audioRecord.stop(); + thread.interrupt() + audioRecord.stop() audioRecord.release() state = State.IDLE } diff --git a/wear/src/main/java/com/birdsounds/identify/presentation/SpeciesList.kt b/wear/src/main/java/com/birdsounds/identify/presentation/SpeciesList.kt new file mode 100644 index 0000000..b5f6767 --- /dev/null +++ b/wear/src/main/java/com/birdsounds/identify/presentation/SpeciesList.kt @@ -0,0 +1,62 @@ +package com.birdsounds.identify.presentation + +import android.util.Log +import java.time.Instant + + +class AScore( + _species: String, + _score: Float, + _timestamp: Long + +) { + + var split_stuff = _species.split("_") + val species = split_stuff[0] + val score = _score + val common_name = split_stuff[1] + val timestamp = _timestamp + fun age(): Long { + var tstamp: Long = Instant.now().toEpochMilli() + return (tstamp - timestamp) + } + + override fun toString(): String { + var tstamp: Long = Instant.now().toEpochMilli() + return common_name + "," + species + "," + score.toString() + ", " + (age() / 1000.0).toString() + "s ago" + } +} + + +object SpeciesList { + var internal_list = mutableListOf() + var do_add_observation = false + fun add_observation(species_in: AScore) { + do_add_observation = false + var idx = 0 + var idx_replace = -1 + for (i in internal_list) + { + if (i.species == species_in.species) + { + do_add_observation = false + idx_replace = idx + } + idx+=1 + } + if (idx_replace >= 0) + { + Log.w(TAG, "Replacing") + internal_list[idx_replace] = species_in + } else { + internal_list.add(species_in) + } +// val list_reranked = internal_list.withIndex().sortedBy { it -> it.value.age() } + internal_list = internal_list.sortedBy({ (it.age()) }).toMutableList() + + + Log.w(TAG, internal_list.toString()) +// internal_list.add(species_in) + } + +} \ No newline at end of file diff --git a/wear/src/main/res/layout/layout.xml b/wear/src/main/res/layout/layout.xml new file mode 100644 index 0000000..41d2ef1 --- /dev/null +++ b/wear/src/main/res/layout/layout.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file