Skip to content

Commit eec6470

Browse files
khanhlvgcopybara-github
authored andcommitted
Create a copy of TFLite sound classification app for compatibility purpose
PiperOrigin-RevId: 373307415
1 parent fa9dd93 commit eec6470

File tree

17 files changed

+1042
-0
lines changed

17 files changed

+1042
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Sound Classifier Android sample.
2+
3+
## Important notice
4+
5+
* See this sample for how to integrate a sound classification model into an
6+
Android app using
7+
[TFLite Task Library](https://github.com/tensorflow/examples/tree/master/lite/examples/sound_classification/android)
8+
* This copy of the Android sound classification sample is to demonstrate how
9+
to integrate sound classification models trained on Teachable Machine.
10+
* This sample is only for compatibility purpose and will be removed in a near
11+
future when Teachable Machine exports models that are compatible with Task
12+
Library.
13+
14+
## Requirements
15+
16+
* Android Studio 4.1 (installed on a Linux, Mac or Windows machine)
17+
* An Android device, or an Android Emulator
18+
19+
## Build and run
20+
21+
### Step 1. Clone the TensorFlow examples source code
22+
23+
Clone the TensorFlow examples GitHub repository to your computer to get the demo
24+
application.
25+
26+
```
27+
git clone https://github.com/tensorflow/examples
28+
```
29+
30+
### Step 2. Import the sample app to Android Studio
31+
32+
Open the TensorFlow source code in Android Studio. To do this, open Android
33+
Studio and select `Import Projects (Gradle, Eclipse ADT, etc.)`, setting the
34+
folder to `examples/lite/examples/sound_classification/android`
35+
36+
### Step 3. Run the Android app
37+
38+
Connect the Android device to the computer and be sure to approve any ADB
39+
permission prompts that appear on your phone. Select `Run -> Run app.` Select
40+
the deployment target in the connected devices to the device on which the app
41+
will be installed. This will install the app on the device.
42+
43+
To test the app, open the app called `TFL Sound Classifier` on your device.
44+
Re-installing the app may require you to uninstall the previous installations.
45+
46+
## Resources used:
47+
48+
* [TensorFlow Lite](https://www.tensorflow.org/lite)
49+
* [Teachable Machine Audio Project](https://teachablemachine.withgoogle.com/train/audio)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
apply plugin: 'com.android.application'
2+
apply plugin: 'kotlin-android'
3+
apply plugin: 'de.undercouch.download'
4+
5+
android {
6+
compileSdkVersion 30
7+
defaultConfig {
8+
applicationId "org.tensorflow.lite.examples.soundclassifier"
9+
minSdkVersion 23
10+
targetSdkVersion 30
11+
versionCode 1
12+
versionName "1.0"
13+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14+
}
15+
16+
aaptOptions {
17+
noCompress "tflite"
18+
}
19+
20+
buildTypes {
21+
release {
22+
minifyEnabled false
23+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24+
}
25+
}
26+
27+
buildFeatures {
28+
viewBinding true
29+
}
30+
31+
compileOptions {
32+
sourceCompatibility 1.8
33+
targetCompatibility 1.8
34+
}
35+
36+
kotlinOptions {
37+
jvmTarget = "1.8"
38+
}
39+
}
40+
41+
// import DownloadModels task
42+
project.ext.ASSET_DIR = projectDir.toString() + '/src/main/assets'
43+
44+
// Download default models; if you wish to use your own models then
45+
// place them in the "assets" directory and comment out below line.
46+
apply from: 'download_model.gradle'
47+
48+
dependencies {
49+
implementation fileTree(dir: "libs", include: ["*.jar"])
50+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
51+
implementation "androidx.core:core-ktx:1.3.1"
52+
implementation "androidx.appcompat:appcompat:1.2.0"
53+
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
54+
implementation "androidx.constraintlayout:constraintlayout:2.0.1"
55+
implementation "androidx.recyclerview:recyclerview:1.1.0"
56+
implementation "com.google.android.material:material:1.2.1"
57+
58+
implementation "org.tensorflow:tensorflow-lite:2.3.0"
59+
implementation "org.tensorflow:tensorflow-lite-select-tf-ops:2.3.0"
60+
implementation "org.tensorflow:tensorflow-lite-support:0.1.0"
61+
implementation "org.tensorflow:tensorflow-lite-metadata:0.1.0"
62+
63+
testImplementation "junit:junit:4.13"
64+
androidTestImplementation "androidx.test.ext:junit:1.1.2"
65+
androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0"
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
task downloadSoundClassificationModelFile(type: Download) {
2+
src 'https://storage.googleapis.com/download.tensorflow.org/models/tflite/sound_classification/snap_clap.tflite'
3+
dest project.ext.ASSET_DIR + '/sound_classifier.tflite'
4+
overwrite false
5+
}
6+
7+
tasks.whenTaskAdded { task ->
8+
if ((task.name == 'assembleDebug') || (task.name == 'assembleRelease')) {
9+
task.dependsOn 'downloadSoundClassificationModelFile'
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="org.tensorflow.lite.examples.soundclassifier">
4+
5+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
6+
7+
<application
8+
android:allowBackup="true"
9+
android:icon="@mipmap/ic_launcher"
10+
android:label="@string/app_name"
11+
android:roundIcon="@mipmap/ic_launcher_round"
12+
android:supportsRtl="true"
13+
android:theme="@style/AppTheme">
14+
<activity
15+
android:name=".MainActivity"
16+
android:exported="true">
17+
<intent-filter>
18+
<action android:name="android.intent.action.MAIN" />
19+
20+
<category android:name="android.intent.category.LAUNCHER" />
21+
</intent-filter>
22+
</activity>
23+
</application>
24+
25+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0 snap
2+
1 _background_noise_
3+
2 clap
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.tensorflow.lite.examples.soundclassifier
18+
19+
import android.Manifest
20+
import android.content.pm.PackageManager
21+
import android.os.Build
22+
import android.os.Bundle
23+
import android.util.Log
24+
import android.view.WindowManager
25+
import androidx.annotation.RequiresApi
26+
import androidx.appcompat.app.AppCompatActivity
27+
import androidx.core.content.ContextCompat
28+
import org.tensorflow.lite.examples.soundclassifier.databinding.ActivityMainBinding
29+
30+
class MainActivity : AppCompatActivity() {
31+
private val probabilitiesAdapter by lazy { ProbabilitiesAdapter() }
32+
33+
private lateinit var soundClassifier: SoundClassifier
34+
35+
override fun onCreate(savedInstanceState: Bundle?) {
36+
super.onCreate(savedInstanceState)
37+
38+
val binding = ActivityMainBinding.inflate(layoutInflater)
39+
setContentView(binding.root)
40+
41+
soundClassifier = SoundClassifier(this, SoundClassifier.Options()).also {
42+
it.lifecycleOwner = this
43+
}
44+
45+
with(binding) {
46+
recyclerView.apply {
47+
setHasFixedSize(true)
48+
adapter = probabilitiesAdapter.apply {
49+
labelList = soundClassifier.labelList
50+
}
51+
}
52+
53+
keepScreenOn(inputSwitch.isChecked)
54+
inputSwitch.setOnCheckedChangeListener { _, isChecked ->
55+
soundClassifier.isPaused = !isChecked
56+
keepScreenOn(isChecked)
57+
}
58+
59+
overlapFactorSlider.value = soundClassifier.overlapFactor
60+
overlapFactorSlider.addOnChangeListener { _, value, _ ->
61+
soundClassifier.overlapFactor = value
62+
}
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
69+
}
70+
probabilitiesAdapter.probabilityMap = resultMap
71+
probabilitiesAdapter.notifyDataSetChanged()
72+
}
73+
74+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
75+
requestMicrophonePermission()
76+
} else {
77+
soundClassifier.start()
78+
}
79+
}
80+
81+
override fun onTopResumedActivityChanged(isTopResumedActivity: Boolean) {
82+
// Handles "top" resumed event on multi-window environment
83+
if (isTopResumedActivity) {
84+
soundClassifier.start()
85+
} else {
86+
soundClassifier.stop()
87+
}
88+
}
89+
90+
override fun onRequestPermissionsResult(
91+
requestCode: Int,
92+
permissions: Array<out String>,
93+
grantResults: IntArray
94+
) {
95+
if (requestCode == REQUEST_RECORD_AUDIO) {
96+
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
97+
Log.i(TAG, "Audio permission granted :)")
98+
soundClassifier.start()
99+
} else {
100+
Log.e(TAG, "Audio permission not granted :(")
101+
}
102+
}
103+
}
104+
105+
@RequiresApi(Build.VERSION_CODES.M)
106+
private fun requestMicrophonePermission() {
107+
if (ContextCompat.checkSelfPermission(
108+
this,
109+
Manifest.permission.RECORD_AUDIO
110+
) == PackageManager.PERMISSION_GRANTED
111+
) {
112+
soundClassifier.start()
113+
} else {
114+
requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), REQUEST_RECORD_AUDIO)
115+
}
116+
}
117+
118+
private fun keepScreenOn(enable: Boolean) =
119+
if (enable) {
120+
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
121+
} else {
122+
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
123+
}
124+
125+
companion object {
126+
const val REQUEST_RECORD_AUDIO = 1337
127+
private const val TAG = "AudioDemo"
128+
}
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.tensorflow.lite.examples.soundclassifier
18+
19+
import android.animation.ObjectAnimator
20+
import android.content.res.ColorStateList
21+
import android.view.LayoutInflater
22+
import android.view.ViewGroup
23+
import android.view.animation.AccelerateDecelerateInterpolator
24+
import androidx.recyclerview.widget.RecyclerView
25+
import org.tensorflow.lite.examples.soundclassifier.databinding.ItemProbabilityBinding
26+
27+
internal class ProbabilitiesAdapter : RecyclerView.Adapter<ProbabilitiesAdapter.ViewHolder>() {
28+
var labelList = emptyList<String>()
29+
var probabilityMap = mapOf<String, Float>()
30+
31+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
32+
val binding =
33+
ItemProbabilityBinding.inflate(LayoutInflater.from(parent.context), parent, false)
34+
return ViewHolder(binding)
35+
}
36+
37+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
38+
val label = labelList[position]
39+
val probability = probabilityMap[label] ?: 0f
40+
holder.bind(position, label, probability)
41+
}
42+
43+
override fun getItemCount() = labelList.size
44+
45+
class ViewHolder(private val binding: ItemProbabilityBinding) :
46+
RecyclerView.ViewHolder(binding.root) {
47+
fun bind(position: Int, label: String, probability: Float) {
48+
with(binding) {
49+
labelTextView.text = label
50+
progressBar.progressBackgroundTintList = progressColorPairList[position % 3].first
51+
progressBar.progressTintList = progressColorPairList[position % 3].second
52+
53+
val newValue = (probability * 100).toInt()
54+
// If you don't want to animate, you can write like `progressBar.progress = newValue`.
55+
val animation =
56+
ObjectAnimator.ofInt(progressBar, "progress", progressBar.progress, newValue)
57+
animation.duration = 100
58+
animation.interpolator = AccelerateDecelerateInterpolator()
59+
animation.start()
60+
}
61+
}
62+
63+
companion object {
64+
/** List of pairs of background tint and progress tint */
65+
private val progressColorPairList = listOf(
66+
ColorStateList.valueOf(0xfff9e7e4.toInt()) to ColorStateList.valueOf(0xffd97c2e.toInt()),
67+
ColorStateList.valueOf(0xfff7e3e8.toInt()) to ColorStateList.valueOf(0xffc95670.toInt()),
68+
ColorStateList.valueOf(0xffecf0f9.toInt()) to ColorStateList.valueOf(0xff714Fe7.toInt()),
69+
)
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)