Hola a todos hoy veremos como detectar los silbidos en android estudio, hago este tutorial para atender a los bastantes correos y mensaje por los diferentes canales que tiene esta pagina, siempre que me han preguntado sobre el tema, yo he contestado que deberían utilizar la librería musicg e inclusive he fomentado a utilizarla por ser una de las pocas librerías free y de fácil entorno, pero lamentablemente al momento de utilizar cualquier librería de reconocimiento de audio es ponerse a programar en un nivel avanzado. Y mis leyentes novatos no han podido implementarla en ese sentido como que deseo echarles una mano, hoy haremos un ejercicio básico , sin mas rodeos empezamos con este tutorial.
como detectar silbido (sonido) en android studio
Contenidos
PASO 1: AGREGAR LIBRERIA
Debemos agregar esta librería en nuestro build.gradle
compile group: 'com.github.fracpete', name: 'musicg', version: '1.4.2.1'
Debería de quedar así
PASO 2: AGREGAR PERMISOS
Debemos ir a nuestro AndroidManifest y agregar los siguientes permisos
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.VIBRATE" />
PASO 3: AGREGANDO UTILIDADES
Primero debemos agregar dentro de la carpeta drawable la siguiente imagen Ahora dentro de la carpeta res debemos crear una carpeta llamada raw , dentro de raw debemos pegar el siguiente archivo audio. https://drive.google.com/file/d/1c7JDIPq5m4-4rrbV92dLoGRR_v1-ZdBJ/view?usp=sharing
PASO 4:CREANDO LAYOUT
Debemos crear los dos archivos layout como se muestran en la siguiente imagen
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff"> <Button android:id="@+id/btnDetener" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="123dp" android:layout_marginEnd="57dp" android:layout_marginTop="8dp" android:text="DETENER" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/btnIniciar" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="1.0" /> <Button android:id="@+id/btnIniciar" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="123dp" android:layout_marginEnd="26dp" android:layout_marginStart="57dp" android:layout_marginTop="8dp" android:text="INICIAR" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/btnDetener" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="1.0" /> <ImageView android:id="@+id/imageView" android:layout_width="228dp" android:layout_height="0dp" android:layout_marginBottom="28dp" android:layout_marginTop="51dp" android:src="@drawable/silbido" app:layout_constraintBottom_toTopOf="@+id/btnIniciar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>
alerta.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:weightSum="100"> <TextView android:id="@+id/tvAlert" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="80" android:gravity="center" android:text="Silbido Detectado" android:textSize="30dp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/restartButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="20" android:text="Restart" android:textStyle="bold" /> <Button android:id="@+id/quitButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="20" android:text="Cancelar" android:textStyle="bold" /> </LinearLayout> </LinearLayout>
PASO 5 : CREANDO CLASES
Debemos crear las seis clases como se muestra en la siguiente imagen
MainActivity
import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.support.v4.app.ActivityCompat; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; public class MainActivity extends Activity{ private Button start,stop; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 1000); ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 1000); } start = (Button) findViewById(R.id.btnIniciar); stop = (Button) findViewById(R.id.btnDetener); start.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { startService(new Intent(getBaseContext(),VocalServicio.class)); } }); stop.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { stopService(new Intent(getBaseContext(),VocalServicio.class)); } }); } }
DetectarSonidoListener
public interface DetectarSonidoListener { public abstract void onWhistleDetected(); }
DetectorThread
import java.util.LinkedList; import com.musicg.api.WhistleApi; import com.musicg.wave.WaveHeader; import android.media.AudioFormat; import android.media.AudioRecord; public class DetectorThread extends Thread{ private RecorderThread grabadora; private WaveHeader waveHeader; private WhistleApi silbidoApi; private volatile Thread _thread; private LinkedList<Boolean> listaResultados = new LinkedList<Boolean>(); private int numWhistles; private int whistleCheckLength = 3; private int whistlePassScore = 3; private DetectarSonidoListener detectarSonidoListener; public DetectorThread(RecorderThread grabadora){ this.grabadora = grabadora; AudioRecord audioRecord = grabadora.getGrabadorAudio(); int bitsPerSample = 0; if (audioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT){ bitsPerSample = 16; } else if (audioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_8BIT){ bitsPerSample = 8; } int channel = 0; // la detección de silbatos solo admite canales mono if (audioRecord.getChannelConfiguration() == AudioFormat.CHANNEL_IN_MONO){ channel = 1; } waveHeader = new WaveHeader(); waveHeader.setChannels(channel); waveHeader.setBitsPerSample(bitsPerSample); waveHeader.setSampleRate(audioRecord.getSampleRate()); silbidoApi = new WhistleApi(waveHeader); } private void initBuffer() { numWhistles = 0; listaResultados.clear(); for (int i = 0; i < whistleCheckLength; i++) { listaResultados.add(false); } } public void start() { _thread = new Thread(this); _thread.start(); } public void stopDetection(){ _thread = null; } public void run() { try { byte[] buffer; initBuffer(); Thread thisThread = Thread.currentThread(); while (_thread == thisThread) { // sonido detectado buffer = grabadora.getFrameBytes(); // analisador de audio if (buffer != null) { // sonido detectado // detección de silbidos //System.out.println("*Whistle:"); boolean isWhistle = silbidoApi.isWhistle(buffer); if (listaResultados.getFirst()) { numWhistles--; } listaResultados.removeFirst(); listaResultados.add(isWhistle); if (isWhistle) { numWhistles++; } if (numWhistles >= whistlePassScore) { // Limpiar buffer initBuffer(); onWhistleDetected(); } // fin de detección de silbatos } else{ // no sound detected if (listaResultados.getFirst()) { numWhistles--; } listaResultados.removeFirst(); listaResultados.add(false); } // terminar el analista de audio } } catch (Exception e) { e.printStackTrace(); } } private void onWhistleDetected(){ if (detectarSonidoListener != null){ detectarSonidoListener.onWhistleDetected(); } } public void setDetectarSonidoListener(DetectarSonidoListener listener){ detectarSonidoListener = listener; } }
RecorderThread
import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; public class RecorderThread extends Thread { private AudioRecord grabadorAudio; private boolean isRecording; private int configurarCanal = AudioFormat.CHANNEL_IN_MONO; private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; private int sampleRate = 44100; private int frameByteSize = 2048; // for 1024 fft size (16bit sample size) byte[] buffer; public RecorderThread(){ int recBufSize = AudioRecord.getMinBufferSize(sampleRate, configurarCanal, audioEncoding); // need to be larger than size of a frame grabadorAudio = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, configurarCanal, audioEncoding, recBufSize); buffer = new byte[frameByteSize]; } public AudioRecord getGrabadorAudio(){ return grabadorAudio; } public boolean isRecording(){ return this.isAlive() && isRecording; } public void startRecording(){ try{ grabadorAudio.startRecording(); isRecording = true; } catch (Exception e) { e.printStackTrace(); } } public void stopRecording(){ try{ grabadorAudio.stop(); grabadorAudio.release(); isRecording = false; } catch (Exception e) { e.printStackTrace(); } } public byte[] getFrameBytes(){ grabadorAudio.read(buffer, 0, frameByteSize); // analizador de audio int totalAbsValue = 0; short sample = 0; float averageAbsValue = 0.0f; for (int i = 0; i < frameByteSize; i += 2) { sample = (short)((buffer[i]) | buffer[i + 1] << 8); totalAbsValue += Math.abs(sample); } averageAbsValue = totalAbsValue / frameByteSize / 2; if (averageAbsValue < 30){ return null; } return buffer; } public void run() { startRecording(); } }
VocalAlerta
import android.app.Activity; import android.content.Intent; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.os.Bundle; import android.view.View; import android.widget.Button; public class VocalAlerta extends Activity implements View.OnClickListener ,OnCompletionListener{ private MediaPlayer miSonido; private Button bReturn; private Button bRestart; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.alerta); stopService(new Intent(getBaseContext(),VocalServicio.class)); initialize(); miSonido = MediaPlayer.create(this,R.raw.alarm); miSonido.setOnCompletionListener(this); miSonido.start(); } private void initialize() { bReturn = (Button) findViewById(R.id.quitButton); bRestart = (Button) findViewById(R.id.restartButton); bReturn.setOnClickListener(this); bRestart.setOnClickListener(this); } @Override public void onClick(View v) { // TODO Auto-generated method stub switch(v.getId()){ case R.id.restartButton : miSonido.release(); Intent i = new Intent(getBaseContext(),VocalServicio.class); startService(i); finish(); break; case R.id.quitButton: miSonido.release(); finish(); break; } } @Override public void onCompletion(MediaPlayer arg0) { // TODO Auto-generated method stub arg0.start(); } }
VocalServicio
import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.widget.Toast; public class VocalServicio extends Service implements DetectarSonidoListener { private DetectorThread detectorThread; private RecorderThread recorderThread; private static final int NOTIFICATION_Id = 001; public static final int DETECT_NONE = 0; public static final int DETECT_WHISTLE = 1; public static int selectedDetection = DETECT_NONE; @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub initNotification(); startDetection(); return START_STICKY; } public void initNotification(){ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.silbido) .setContentTitle("Detección de silbidos") .setContentText("La detección de silbidos está activada."); Intent resultIntent = new Intent(this, MainActivity.class); PendingIntent resultPendingIntent = PendingIntent.getActivity( this,0, resultIntent,PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(resultPendingIntent); NotificationManager mNotifyMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); mNotifyMgr.notify(NOTIFICATION_Id, mBuilder.build()); } public void startDetection(){ selectedDetection = DETECT_WHISTLE; recorderThread = new RecorderThread(); recorderThread.start(); detectorThread = new DetectorThread(recorderThread); detectorThread.setDetectarSonidoListener(this); detectorThread.start(); Toast.makeText(this, "Servicio iniciado", Toast.LENGTH_LONG).show(); } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); if (recorderThread != null) { recorderThread.stopRecording(); recorderThread = null; } if (detectorThread != null) { detectorThread.stopDetection(); detectorThread = null; } selectedDetection = DETECT_NONE; Toast.makeText(this, "Servicio detenido", Toast.LENGTH_LONG).show(); stopNotification(); } @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } @Override public void onWhistleDetected() { Intent intent = new Intent(this,VocalAlerta.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); Toast.makeText(this, "Silbido detectado", Toast.LENGTH_LONG).show(); this.stopSelf(); } public void stopNotification(){ NotificationManager mNotifyMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); mNotifyMgr.cancel(NOTIFICATION_Id); } }
PASO 6: LLAMAR NUESTRO SERVICIO
Debemos verificar que nuestro androidManifest esta escrito de forma correcta, dentro debemos llamar a nuestro servicio VocalServicio» como se muestra en las lineas 35-38 y también a nuestra clase VocalAlerta como se muestra en las lineas del 24 al 33
AndroidManifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.androfast.server.appsilbido"> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.VIBRATE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".VocalAlerta" android:label="@string/app_name" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.VOCALALERT" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <service android:name=".VocalServicio" android:enabled="true" android:exported="true" /> </application> </manifest>
Con esto terminamos el tutorial, espero les aya gustado y que les sirva para sus proyectos, si quieren leer mas de la librería les invito a visitar el siguiente repositorio : musicg esta librería es muy utilizada en muchos ámbitos como el musical, científico y por supuesto el de programación, así que aprovechen que es gratuita y busque toda la info que necesiten en san google, hasta un próximo tutorial, se me cuidan!!!.
Hola soy Alex Céspedes fundador de ANDROFAST, programo algunas cosas por diversión, me gusta aprender cosas nuevas y estoy pendiente de todo lo que tenga que ver con tecnología. Este blog lo cree para todas las personas que tengan dificultades en la programación, para ser sincero nunca fui bueno y reprobé algunos cursos de programación, pero mis ganas de aprender pudieron más. SI YO PUEDO TU PUEDES ANIMO!