This commit is contained in:
2025-04-02 13:20:29 -04:00
parent 7ff235fb3f
commit e73d52d221
26 changed files with 1511 additions and 295 deletions

View File

@@ -55,12 +55,16 @@ dependencies {
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation("androidx.datastore:datastore-preferences:1.1.3")
implementation("org.tensorflow:tensorflow-lite:2.6.0")
// implementation("com.google.android.gms:play-services-tflite:20.0.0")
implementation("uk.me.berndporr:iirj:1.7")
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.datastore.core.android)
implementation(libs.core.ktx)
// implementation(libs.litert)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)

View File

@@ -6,7 +6,7 @@ import java.io.IOException
class Downloader(mainActivity: MainActivity) {
private val settings = Settings();
private val settings = Settings;
private var activity: MainActivity = mainActivity;
private var context: Context = activity.applicationContext;

View File

@@ -1,15 +1,24 @@
@file:Suppress("DEPRECATION")
package com.birdsounds.identify
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.core.app.ActivityCompat
import java.util.Locale
object Location {
private var locationListenerGPS: LocationListener? = null
@@ -30,7 +39,10 @@ object Location {
) {
val locationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (locationListenerGPS == null) locationListenerGPS = object : LocationListener {
@SuppressLint("SetTextI18n")
override fun onLocationChanged(location: Location) {
Log.w(TAG, "Got location changed");
while (!soundClassifier.is_model_ready()) {
@@ -39,7 +51,35 @@ object Location {
Log.w(TAG, "Sound classifier is ready");
soundClassifier.runMetaInterpreter(location)
soundClassifier.runMetaInterpreter(location)
val activity = context as? Activity;
activity?.let {
val text_species: TextView = it.findViewById(R.id.local_species)
text_species.text = local_species;
val loc_lon: TextView = it.findViewById(R.id.location_long)
loc_lon.text =
"Longitude: ${location.longitude}"
val loc_lat: TextView = it.findViewById(R.id.location_lat)
loc_lat.text =
"Latitude: ${location.latitude}"
val loc_string: TextView = it.findViewById(R.id.location_string);
val geocoder = Geocoder(context, Locale.getDefault())
val addresses: MutableList<Address>? =
geocoder.getFromLocation(location.latitude, location.longitude, 1)
if (addresses?.isNotEmpty() == true) {
val address: Address = addresses[0]
// loc_string.text = address.locality.toString() + ", " + address.adminArea.toString() + " " + address.countryName.toString()
loc_string.text = address.getAddressLine(0).toString()
// Log.w(TAG, address.toString())
}
}
}
@Deprecated("")
@@ -53,7 +93,7 @@ object Location {
}
}
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 60000, 0f,
LocationManager.PASSIVE_PROVIDER, 60000, 0f,
locationListenerGPS!!
)
}

View File

@@ -1,16 +1,42 @@
package com.birdsounds.identify
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.Manifest
import android.preference.PreferenceManager
import android.util.Log
import android.widget.SeekBar
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.wearable.ChannelClient
import com.google.android.gms.wearable.Wearable
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.time.Instant
private var updateJob: Job? = null
private var updateCounter = 0
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
val Any.TAG: String
get() {
@@ -19,11 +45,39 @@ val Any.TAG: String
}
class SynchronousDataStore(private val context: Context) {
// Define keys
companion object {
val THRESHOLD_KEY = intPreferencesKey("user_age")
}
// Synchronous write operations
fun saveThreshold(value: Int) {
runBlocking {
context.dataStore.edit { preferences ->
preferences[THRESHOLD_KEY] = value;
}
}
}
fun getThreshold(): Int {
return runBlocking {
context.dataStore.data.first()[THRESHOLD_KEY] ?: 50
}
}
}
class MainActivity : AppCompatActivity() {
// private lateinit var soundClassifier: SoundClassifier
val REQUEST_PERMISSIONS = 1337
private lateinit var dataStore: SynchronousDataStore
private lateinit var last_message_delay: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@@ -49,6 +103,34 @@ class MainActivity : AppCompatActivity() {
insets
}
val thresholdText = findViewById<TextView>(R.id.threshold_value_text)
val seekBar = findViewById<SeekBar>(R.id.threshold_set_scale_bar)
last_message_delay = findViewById<TextView>(R.id.last_message_delay)
dataStore = SynchronousDataStore(applicationContext)
Settings.threshold = dataStore.getThreshold();
seekBar.progress = Settings.threshold
thresholdText.text = String.format("%.2f", Settings.threshold/100f)
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
@SuppressLint("DefaultLocale")
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
var actualValue = progress
dataStore.saveThreshold(actualValue);
Settings.threshold = actualValue;
thresholdText.text = String.format("%.2f", actualValue/100f)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
}
companion object {
var soundClassifier: SoundClassifier? = null
@@ -57,6 +139,34 @@ class MainActivity : AppCompatActivity() {
// }
}
@SuppressLint("SetTextI18n")
override fun onResume() {
super.onResume()
// Start periodic updates using coroutines
updateJob = lifecycleScope.launch {
while (isActive) { // isActive is a property of the coroutine scope
updateCounter++
if (last_message_time == 0.toLong())
{
last_message_delay.text = "No messages received"
} else
{
last_message_delay.text = "Last message: ${(Instant.now().toEpochMilli() - last_message_time)/1000F.toInt()} seconds ago"
}
// last_message_delay.text = "Update count: $updateCounter"
delay(500) // Update every 1 second
}
}
}
override fun onPause() {
super.onPause()
// Cancel the coroutine when activity is not visible
updateJob?.cancel()
}
private fun requestPermissions() {
val perms = mutableListOf<String>()

View File

@@ -10,6 +10,19 @@ import decodeAACToPCM
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.time.Instant
var last_message_time = 0L;
fun ByteArray.toLong(): Long {
require(size <= 8) { "ByteArray too large to fit in Long" }
var result = 0L
for (byte in this) {
result = (result shl 8) or (byte.toLong() and 0xFF)
}
return result
}
class MessageListenerService : WearableListenerService() {
@@ -30,6 +43,10 @@ class MessageListenerService : WearableListenerService() {
}
var tstamp_bytes = p0.data.copyOfRange(0, Long.SIZE_BYTES)
last_message_time = tstamp_bytes.toLong()
var audio_bytes_og = p0.data.copyOfRange(Long.SIZE_BYTES, p0.data.size)
@@ -63,8 +80,12 @@ class MessageListenerService : WearableListenerService() {
var sorted_list = soundclassifier.executeScoring(short_array)
Log.w(TAG, "FINISHED SCORING");
Log.w(TAG, "")
for (i in 0 until 5) {
val threshold = Settings.threshold/100f
for (i in 0 until 10) {
val score = sorted_list[i].value
if (score < threshold) {
continue
}
val index = sorted_list[i].index
val species_name = soundclassifier.labelList[index]
Log.w(TAG, species_name + ", " + score.toString())

View File

@@ -1,9 +1,11 @@
package com.birdsounds.identify
class Settings {
object Settings {
var local_model_file: String = "2024_08_16_audio_model.tflite"
var pkg_model_file: String = "2024_08_16/audio-model.tflite"
var local_meta_model_file: String = "2024_08_16_meta_model.tflite"
var pkg_meta_model_file: String = "2024_08_16/meta-model.tflite"
var threshold: Int = 50;
}

View File

@@ -26,6 +26,8 @@ import uk.me.berndporr.iirj.Butterworth
import kotlin.math.sin
var local_species = ""
class SoundClassifier(
context: Context,
private val options: Options = Options()
@@ -81,7 +83,7 @@ class SoundClassifier(
/** Number of output classes of the TFLite model. */
private var modelNumClasses = 0
private var metaModelNumClasses = 0
private var settings: Settings = Settings();
private var settings = Settings;
/** Used to hold the real-time probabilities predicted by the model for the output classes. */
private lateinit var predictionProbs: FloatArray
@@ -274,6 +276,17 @@ class SoundClassifier(
metaOutputBuffer.rewind()
metaOutputBuffer.get(metaPredictionProbs) // Copy data to metaPredictionProbs.
var cloned = metaPredictionProbs.clone()
val sorted_indices = cloned.withIndex().sortedByDescending { it.value }.map{it.index}
local_species = "Most likely:\n"
for (i in 0..5) {
local_species += " " +labelList[sorted_indices[i]].split("_")[1]
local_species += "\n";
}
for (i in metaPredictionProbs.indices) {
metaPredictionProbs[i] =

View File

@@ -7,13 +7,89 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingTop="15dp"
android:text="Threshold:"
android:textSize="34sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="55dp"
android:orientation="horizontal">
<TextView
android:id="@+id/threshold_value_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="4"
android:gravity="center"
android:text="Threshold:"
android:textAlignment="center"
android:textSize="28sp" />
<SeekBar
android:id="@+id/threshold_set_scale_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center|fill_horizontal|fill_vertical"
android:layout_weight="1"
android:max="100"
android:min="0" />
</LinearLayout>
<TextView
android:id="@+id/location_string"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingTop="15dp"
android:paddingRight="5dp"
android:text="Location:"
android:textSize="34sp" />
<TextView
android:id="@+id/location_lat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingTop="15dp"
android:paddingRight="5dp"
android:text="Latitude:"
android:textSize="20sp" />
<TextView
android:id="@+id/location_long"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="Longitude:"
android:textSize="20sp" />
<TextView
android:id="@+id/local_species"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:text="Local Species:"
android:textSize="20sp" />
<TextView
android:id="@+id/last_message_delay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Last Message" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>