Bienvenidos sean a este post, en el post de hoy seguiremos un poco fuera del proyecto y hablaremos sobre una clase que nos permitira controlar sonidos para nuestras apps.
Esta clase nos permite manipular toda clase de sonidos en nuestro dispositivo, ya sea desde efectos de sonido hasta la musica aunque no es lo recomendable esto ultimo pero ya veremos porque, los formatos que puede utilizar esta clase van desde un formato .wav hasta .mp3 incluyendo el .ogg, como esta clase trabaja con un id nos permite ejecutar nuestro sonido a traves de un thread paralelo sin molestar a la performance del dispositivo, para entender todo el concepto vamos a crear el siguiente proyecto:
- Dispositivos: Phone and Tablet
- Actividad: Empty Activity
- Nombre: SoundPool
- Nombre de paquete: org.example.soundpool
- API Minimo: API 23 (Android 6.0)
Si se dieron cuenta de un detalle es que cambiamos a la version de la API pero porque lo hacemos asi? En realidad lo hacemos para poder ver como se utiliza esta clase en una API superior a la version 21, cuando veamos este tema en nuestro juego Pong lo haremos con una API mas inferior, con esto aclarado pasemos a hacer unas modificaciones antes de centrarnos en nuestro codigo, primero haremos click con el boton derecho sobre la raiz de nuestra app, es decir donde dice app, seleccionaremos New -> Folder -> Assets Folder, nos aparecera la siguiente ventana

En este seleccionaremos donde ubicaremos a nuestra carpeta assets, para este caso debemos dejarlo en main, es decir dejarlo tal como aparece, pulsamos Finish para proceder a crear nuestra nueva carpeta, nos quedara de la siguiente forma

Con esto ya tenemos nuestro contenedor para los recursos del tipo sonido y otros, nuestro siguiente paso sera descargar el siguiente archivo
Una vez descargado este archivo, deben ir hasta la carpeta donde esta y presionen Ctrl+C para copiar este archivo y luego hacen Ctrl+V sobre la carpeta creada anteriormente, nos aparecera un nuevo cuadro donde lo dejaremos como aparece y presionaremos Ok para proceder con la copia, una vez realizada nos quedara de la siguiente forma

Nota: Pueden usar otro archivo si lo desean pero recuerden cambiar el nombre del nuevo en lugar del que usare para referenciar al archivo.
En este caso estoy usando un archivo de la siguiente pagina:
Aca podran descargar mucha musica libre de «royalties» y podran usarlo en cualquiera de sus proyectos siempre y cuando mencionen el origen, tambien dispone de temas pagos para evitar estos inconvenientes, con todo esto configurado podemos proceder a modificar nuestros codigos, para ello primero vamos a modificar nuestro layout con el siguiente codigo:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:orientation="horizontal"
tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play"
android:onClick="reproducir"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop"
android:onClick="detener"/>
</LinearLayout>
Este es un simple layout donde tendremos uno de tipo LinearLayout en sentido horizontal, o mejor dicho su orientacion, luego tendra dos botones dentro de este layout, el primero sera para reproducir nuestro sonido y el segundo sera para detenerlo, en ambos casos usaremos a onclick para llamar a sus respectivas funciones, reproducir y detener, a pesar del error que tenemos porque las funciones aun no existen podemos continuar con nuestra clase Java, para ello primero agregaremos las siguiente variables en la clase MainActivity:
SoundPool sp;
int ahoraSonando = -1;
int idSonido = -1;
float volumen = 1;
En este bloque primero crearemos un objeto de tipo SoundPool llamado sp, despues tendremos la variable llamada ahoraSonando, la cual usaremos para saber si se esta reproduciendo o no, no es exactamente esto pero si nos servira para esta tarea, luego una variable llamada idSonido para guardar el id del sonido, de esto hablaremos mas adelante, en ambos casos le asignamos los valores -1 por asignarle un valor y no es necesario que sea ese valor pero si se necesita uno, por ultimo tenemos una variable de float llamada volumen para controlar el nivel del sonido, este valor va del rango de 0 a 1 y por este motivo debe ser float para que cubra los valores de 0.1, 0.2, etc., nuestra siguiente paso sera agregar el siguiente condicional dentro del metodo onCreate:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
AudioAttributes atributos = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
sp = new SoundPool.Builder()
.setMaxStreams(5)
.setAudioAttributes(atributos)
.build();
} else {
sp = new SoundPool(5, AudioManager.STREAM_MUSIC,0);
}
En este caso tenemos un condicional que verifica si la version de nuestro Android, Build.VERSION.SDK_INT, es mayor o igual a Lollipop, Build.VERSION_CODES.LOLLIPOP, y esto lo hacemos porque a partir de la API 21 la forma de trabajar con la SoundPool se modifico, esto es mas que nada basicamente para evitar problemas de compatibilidad a la hora de ejecutar nuestra aplicacion, en este caso al haberlo diseñado con una API 23 podremos probarlo con equipos de Android 6.0 para arriba pero volvamos al condicional.
Nota: Para poder probar la app tuve que crearme un nuevo equipo con el emulador sino tienen uno traten de crearlo, les recomiendo este post, porque mas adelante seguramente necesitaremos hablar sobre los permisos de Marshmallow en adelante.
El condicional estara dividido en dos partes, la primera sera para setear a SoundPool de la nueva forma y la segunda para la version vieja, comencemos con la mas nueva, para este caso debemos aclarar una cosa primero: las lineas siempre quedan delimitadas hasta encontrar un punto y coma (;) ya que Java no le importa los espacios que encuentre en la linea siempre y cuando al final tenga el punto y coma, una practicidad que nos provee esta forma de trabajar es la posibilidad de poder unir varios metodos por medio del punto y a su vez tenerlos en varias lineas, con esto aclarado nuestra primera creacion es un objeto de tipo AudioAttributes llamado atributos, en esta estableceremos el tipo de uso y el tipo de contenido, veamos como es esta «linea»:
AudioAttributes atributos = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
Como pueden ver en realidad no la iniciamos con un constructor sino con el metodo Builder de la clase, luego por medio del metodo setUsage estableceremos el tipo de uso, en este caso tenemos una constante de tipo generico y luego seteamos el tipo de contenido (tambien de indole generico), si siguen mirando podran encontrar algunas constantes para usos mas precisos y por ultimo va el metodo build el cual sera el encargado de hacer la magia y construir el objeto, esta forma de trabajar ha reemplazado en muchas clases la forma de crear objetos y definirlos, se pueden usar mas metodos pero para nuestro ejemplo es mas que suficiente, por ultimo tendremos la linea encargada de crear a sp en memoria, veamos como es su bloque:
sp = new SoundPool.Builder()
.setMaxStreams(5)
.setAudioAttributes(atributos)
.build();
En esta «linea» volvemos a usar al metodo Builder pero primero setearemos el maximo de streams (es decir la cantidad de veces que se reproduce al mismo tiempo), luego pasaremos los atributos que establecimos anteriormente y por ultimo usamos el build para cerrar la linea y crear nuestro objeto, con esto ya tenemos nuestro objeto SoundPool establecido en memoria y presto para trabajar, pasemos a ver la segunda condicion.
En esta condicion primero y principal usaremos al constructor de SoundPool donde como primer parametro estableceremos el maximo de streams, despues como segundo atributo usaremos a la constante STREAM_MUSIC de AudioManager por ser el mas generico y por ultimo estableceremos la prioridad, si bien a simple vista este resulta mas practico, con el anterior podemos definir de mejor forma nuestros atributos y por ende mejorar el rendimiento de nuestro sonido, si observan en el editor figura como tachado esto es debido a que esta forma de trabajar quedo obsoleta (deprecated) y se recomienda no usarla, en Java en general por mas que queden obsoletas se siguen usando (por ejemplo el preferenceScreen) siempre y cuando no descubran una vulnerabilidad grave que pueda comprometar al sistema, por este motivo siempre es mejor adaptarse a las nuevas formas de trabajo o reemplazar con las clases correspondientes, si se preguntan que sucede cuando se elimina un metodo o clase en los equipos viejos, por lo general tienden a sacar una equivalencia o se lo integra en otra clase para mantener la compatibilidad, salvo que sea muy viejo, continuemos con nuestro codigo y para ello vamos a agregar el siguiente bloque en onCreate:
try{
AssetManager assetManager = this.getAssets();
AssetFileDescriptor descriptor;
descriptor = assetManager.openFd("retrosoul.ogg");
idSonido = sp.load(descriptor,0);
} catch (IOException e){
Log.e("Error","Fallo la carga del archivo");
}
En este caso usaremos un bloque try/catch para manejar la falta de nuestro archivo, para esta ocasion usaremos a AssetManager del cual crearemos un objeto y por medio de getAssets obtendremos todos los recursos que tengamos en el directorio assets, despues crearemos un objeto de tipo AssetFileDescriptor llamado descriptor, en este objeto usaremos a openFd para poder acceder a nuestro archivo «retrosoul.ogg«, despues les comentare el porque de este formato, con el valor establecido nuestro siguiente paso sera cargar en idSonido al archivo, para ello usamos a load de SoundPool por medio sp, el primer dato sera el valor en descriptor y el otro sera la prioridad, despues tenemos el catch correspondiente para notificarnos en caso de falla, con esto completamos a onCreate y tenemos nuestro objeto con la musica cargada, nuestra siguiente modificacion sera los metodos faltantes:
public void reproducir(View vista){
ahoraSonando=sp.play(idSonido,volumen,volumen,0,0,1);
}
public void detener(View vista){
sp.stop(ahoraSonando);
}
En el primer metodo, reproducir, utilizaremos a ahoraSonando al cual le asignaremos a la accion del metodo play en sp, a continuacion te detallo cada uno de sus campos:
- archivo, el objeto con el sonido cargado en este caso idSonido
- volumen parlante izquierdo
- volumen parlante derecho
- la prioridad
- el loop, la cantidad de veces que se repita y un valor -1 es igual a infinito
- rate, modificamos la frecuencia de reproduccion
En este caso todos los valores, salvo el primero, son de tipo float y el rate puede ir desde 0.5 a 2.0, con esto ya podemos escuchar como se reproduce nuestro tema, la siguiente funcion solamente se encarga de detener nuestro sonido, con esto ya tenemos nuestra aplicacion terminada veamos el codigo final:
MainActivity.java
package org.example.soundpool;
import androidx.appcompat.app.AppCompatActivity;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
SoundPool sp;
int ahoraSonando = -1;
int idSonido = -1;
float volumen = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
AudioAttributes atributos = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
sp = new SoundPool.Builder()
.setMaxStreams(5)
.setAudioAttributes(atributos)
.build();
} else {
sp = new SoundPool(5, AudioManager.STREAM_MUSIC,0);
}
try{
AssetManager assetManager = this.getAssets();
AssetFileDescriptor descriptor;
descriptor = assetManager.openFd("retrosoul.ogg");
idSonido = sp.load(descriptor,0);
} catch (IOException e){
Log.e("Error","Fallo la carga del archivo");
}
}
public void reproducir(View vista){
ahoraSonando=sp.play(idSonido,volumen,volumen,0,0,1);
}
public void detener(View vista){
sp.stop(ahoraSonando);
}
}
Con todo esto comentado y mostrado procedamos a ejecutar nuestra aplicacion como se ve en el siguiente video
Como se puede ver se ejecuta de manera perfecta pero esta clase en particular tiene una serie de detalles que nos pueden jugar a favor o en contra:
- una contra es que no encontre una manera eficiente de manejar archvos de sonido muy grandes, por ejemplo la musica
- una a favor es que permite manejar sonidos cortos por medio de threads sin afectar la performance
- permite manejar cualquier clase de archivo pero preferentemente .ogg o .wav, los .mp3 por ejemplo no funcionan muy bien
- para reproducir archivos de musica recomiendo usar a MediaPlayer
- al trabajar con threads nos va a dar una mejor respuesta a la hora de usarlo con la deteccion de colisiones
Nota: Para que se pueda ver correctamente el video tuve que bajar la frecuencia del archivo original de 44400 KHz a 16000 Khz, convertirlo de .MP3 a .OGG y lograr que el archivo no supere por mucho los 1.5 MB pero se sugiere que no supere 1 MB para poder ser reproducido correctamente.
En resumen, hoy hemos visto como es SoundPool, como trabaja, como nos puede ayudar, algunos beneficios que nos brinda, algunas contras que nos trae, vimos un ejemplo para ponerlo en practica, hablamos de los assets y por ultimos los pros y contras de trabajar con SoundPool, espero les haya sido util sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.
Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.

Tambien podes donar
Es para mantenimiento del sitio, gracias!
$1.00