@@ -18,84 +18,150 @@ package org.tensorflow.lite.examples.soundclassifier
1818
1919import android.Manifest
2020import android.content.pm.PackageManager
21+ import android.media.AudioRecord
2122import android.os.Build
2223import android.os.Bundle
24+ import android.os.Handler
25+ import android.os.HandlerThread
2326import android.util.Log
2427import android.view.WindowManager
2528import androidx.annotation.RequiresApi
2629import androidx.appcompat.app.AppCompatActivity
2730import androidx.core.content.ContextCompat
31+ import androidx.core.os.HandlerCompat
2832import org.tensorflow.lite.examples.soundclassifier.databinding.ActivityMainBinding
33+ import org.tensorflow.lite.task.audio.classifier.AudioClassifier
34+
2935
3036class MainActivity : AppCompatActivity () {
3137 private val probabilitiesAdapter by lazy { ProbabilitiesAdapter () }
3238
33- private lateinit var soundClassifier: SoundClassifier
39+ private var audioClassifier: AudioClassifier ? = null
40+ private var audioRecord: AudioRecord ? = null
41+ private var classificationInterval = 500L // how often should classification run in milli-secs
42+ private lateinit var handler: Handler // background thread handler to run classification
3443
3544 override fun onCreate (savedInstanceState : Bundle ? ) {
3645 super .onCreate(savedInstanceState)
3746
3847 val binding = ActivityMainBinding .inflate(layoutInflater)
3948 setContentView(binding.root)
4049
41- soundClassifier = SoundClassifier (this , SoundClassifier .Options ()).also {
42- it.lifecycleOwner = this
43- }
44-
4550 with (binding) {
4651 recyclerView.apply {
47- setHasFixedSize(true )
48- adapter = probabilitiesAdapter.apply {
49- labelList = soundClassifier.labelList
50- }
52+ setHasFixedSize(false )
53+ adapter = probabilitiesAdapter
5154 }
5255
56+ // Input switch to turn on/off classification
5357 keepScreenOn(inputSwitch.isChecked)
5458 inputSwitch.setOnCheckedChangeListener { _, isChecked ->
55- soundClassifier.isPaused = ! isChecked
59+ if ( isChecked) startAudioClassification() else stopAudioClassification()
5660 keepScreenOn(isChecked)
5761 }
5862
59- overlapFactorSlider.value = soundClassifier.overlapFactor
60- overlapFactorSlider.addOnChangeListener { _, value, _ ->
61- soundClassifier.overlapFactor = value
63+ // Slider which control how often the classification task should run
64+ classificationIntervalSlider.value = classificationInterval.toFloat()
65+ classificationIntervalSlider.setLabelFormatter { value: Float ->
66+ " ${value.toInt()} ms"
6267 }
63- }
64-
65- soundClassifier.probabilities.observe(this ) { resultMap ->
66- if (resultMap.isEmpty() || resultMap.size > soundClassifier.labelList.size) {
67- Log .w(TAG , " Invalid size of probability output! (size: ${resultMap.size} )" )
68- return @observe
68+ classificationIntervalSlider.addOnChangeListener { _, value, _ ->
69+ classificationInterval = value.toLong()
70+ stopAudioClassification()
71+ startAudioClassification()
6972 }
70- probabilitiesAdapter.probabilityMap = resultMap
71- probabilitiesAdapter.notifyDataSetChanged()
7273 }
7374
75+ // Create a handler to run classification in a background thread
76+ val handlerThread = HandlerThread (" backgroundThread" )
77+ handlerThread.start()
78+ handler = HandlerCompat .createAsync(handlerThread.looper)
79+
80+ // Request microphone permission and start running classification
7481 if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
7582 requestMicrophonePermission()
7683 } else {
77- soundClassifier.start ()
84+ startAudioClassification ()
7885 }
86+
87+ }
88+
89+ private fun startAudioClassification () {
90+ // If the audio classifier is initialized and running, do nothing.
91+ if (audioClassifier != null ) return ;
92+
93+ // Initialize the audio classifier
94+ val classifier = AudioClassifier .createFromFile(this , MODEL_FILE )
95+ val audioTensor = classifier.createInputTensorAudio()
96+
97+ // Initialize the audio recorder
98+ val record = classifier.createAudioRecord()
99+ record.startRecording()
100+
101+ // Define the classification runnable
102+ val run = object : Runnable {
103+ override fun run () {
104+ val startTime = System .currentTimeMillis()
105+
106+ // Load the latest audio sample
107+ audioTensor.load(record)
108+ val output = classifier.classify(audioTensor)
109+
110+ // Filter out results above a certain threshold, and sort them descendingly
111+ val filteredModelOutput = output[0 ].categories.filter {
112+ it.score > MINIMUM_DISPLAY_THRESHOLD
113+ }.sortedBy {
114+ - it.score
115+ }
116+
117+ val finishTime = System .currentTimeMillis()
118+
119+ Log .d(TAG , " Latency = ${finishTime - startTime} ms" )
120+
121+ // Updating the UI
122+ runOnUiThread {
123+ probabilitiesAdapter.categoryList = filteredModelOutput
124+ probabilitiesAdapter.notifyDataSetChanged()
125+ }
126+
127+ // Rerun the classification after a certain interval
128+ handler.postDelayed(this , classificationInterval)
129+ }
130+ }
131+
132+ // Start the classification process
133+ handler.post(run)
134+
135+ // Save the instances we just created for use later
136+ audioClassifier = classifier
137+ audioRecord = record
138+ }
139+
140+ private fun stopAudioClassification () {
141+ handler.removeCallbacksAndMessages(null )
142+ audioRecord?.stop()
143+ audioRecord = null
144+ audioClassifier = null
79145 }
80146
81147 override fun onTopResumedActivityChanged (isTopResumedActivity : Boolean ) {
82148 // Handles "top" resumed event on multi-window environment
83149 if (isTopResumedActivity) {
84- soundClassifier.start ()
150+ startAudioClassification ()
85151 } else {
86- soundClassifier.stop ()
152+ stopAudioClassification ()
87153 }
88154 }
89155
90156 override fun onRequestPermissionsResult (
91- requestCode : Int ,
92- permissions : Array <out String >,
93- grantResults : IntArray
157+ requestCode : Int ,
158+ permissions : Array <out String >,
159+ grantResults : IntArray
94160 ) {
95161 if (requestCode == REQUEST_RECORD_AUDIO ) {
96162 if (grantResults.isNotEmpty() && grantResults[0 ] == PackageManager .PERMISSION_GRANTED ) {
97163 Log .i(TAG , " Audio permission granted :)" )
98- soundClassifier.start ()
164+ startAudioClassification ()
99165 } else {
100166 Log .e(TAG , " Audio permission not granted :(" )
101167 }
@@ -105,11 +171,11 @@ class MainActivity : AppCompatActivity() {
105171 @RequiresApi(Build .VERSION_CODES .M )
106172 private fun requestMicrophonePermission () {
107173 if (ContextCompat .checkSelfPermission(
108- this ,
109- Manifest .permission.RECORD_AUDIO
110- ) == PackageManager .PERMISSION_GRANTED
174+ this ,
175+ Manifest .permission.RECORD_AUDIO
176+ ) == PackageManager .PERMISSION_GRANTED
111177 ) {
112- soundClassifier.start ()
178+ startAudioClassification ()
113179 } else {
114180 requestPermissions(arrayOf(Manifest .permission.RECORD_AUDIO ), REQUEST_RECORD_AUDIO )
115181 }
@@ -125,5 +191,7 @@ class MainActivity : AppCompatActivity() {
125191 companion object {
126192 const val REQUEST_RECORD_AUDIO = 1337
127193 private const val TAG = " AudioDemo"
194+ private const val MODEL_FILE = " yamnet.tflite"
195+ private const val MINIMUM_DISPLAY_THRESHOLD : Float = 0.3f
128196 }
129197}
0 commit comments