Tabla de Contenidos
Reproducir sonido con ESP32
Introducción
Hoy vamos a ver una de las cosas más chulas que he aprendido hasta el momento del ESP32, una maquinita que nunca deja de sorprenderme no sólo por su velocidad y capacidad de memoria, sino también por detalles como el que voy a tratar de explicar a continuación.
Aunque siempre me he declarado un enamorado de los ESP8266, tengo que reconocer que por un pequeño coste casi imperceptible, los ESP32 disponen de una serie de características que los convierten en dispositivos mucho más versátiles si cabe, y una de esas características es la disponibilidad de dos canales DAC.
¿Y qué narices es eso de «DAC»? Pues fácil: son las siglas de «Digital to Analog Converter», o sea, «Convertidor Digital a Analógico». Básicamente consiste en dos salidas del ESP32 que convierten un dato (en este caso, de ocho bits, de 0 a 255) en un voltaje de salida concreto entre los 0 y los 3,3V. Gracias a esto, podemos utilizar estas salidas para convertir sonidos guardados en formato digital a una corriente eléctrica modulada en amplitud que un altavoz puede convertir a su vez en un sonido audible, lo cual es el objetivo final de este post.
El montaje, como se puede ver, no puede ser más simple. Sólo necesitamos el ESP32 y un altavoz pequeño de 8 Ohm. Si queremos aumentar el volumen podemos incluir un amplificador, o incluso un jack de entrada de audio para otro dispositivo, pero de momento con esto nos bastará para montar el ejemplo.
Limitaciones de espacio del ESP32
Los microcontroladores ESP32, además de una memoria interna de unos 500KB para almacenar programas y datos, disponen de una memoria flash «externa» (aunque la mayoría de las veces la encontraréis «incrustada» en la propia placa del controlador) que puede llegar hasta los 16MB. En modelos de última generación esta memoria incluso puede ser mucho mayor, pero nos centraremos en los modelos convencionales y que pueden encontrarse en cualquier tienda online.
Lo normal, por economía, es tener algún ESP32 con 4MB de flash, así que los datos de sonido que queramos reproducir deben caber en este espacio. Como además estamos hablando de sonido sin comprimir, sólo disponemos de algunos segundos de reproducción, a menos que hagamos uso de un módulo lector de tarjetas SD. En este caso, por primar la sencillez, veremos cómo reproducir sonido directamente desde la memoria del ESP32.
Pero para ello primero debemos trabajar un poco con el archivo de sonido. Usaremos un archivo mp3, lo convertiremos a wav, eliminando con ello la compresión, y luego lo introduciremos dentro de la memoria del ESP32 como una matriz de datos:
Convertir sonido a archivo wav
Para convertir el archivo mp3 a wav utilizaremos la aplicación «Audacity», disponible tanto para Windows como para Linux. Con «Archivo - Abrir» seleccionamos el archivo de sonido sobre el que vamos a trabajar (que en mi caso va a ser un mp3 de Los Beatles). Al cargarse el archivo, la pantalla de Audacity se vería así: Como he explicado antes, la memoria interna del ESP32 no permite cargar una canción descomprimida completa por falta de espacio, así que seleccionamos unos diez segundos de canción pulsando dentro del espectro de sonido y arrastrando el ratón hasta la marca deseada: Copiamos con CTRL+C (copiar), y selecionamos «Archivo - Nuevo», con lo que se nos abre una nueva ventana vacía de Audacity. Allí pulsamos CTRL+V (pegar). Con esto tendremos el intervalo de sonido que queremos reproducir en una ventana aparte.
Para ver el espectro completo de la selección pulsamos CTRL+F, y podemos cortar el tiempo se silencio del principio con CTRL+X. Ahora Audacity aparecerá de esta forma: Como se puede ver, es un sonido en estéreo, con un canal derecho y otro izquierdo, y aunque el ESP32 dispone de dos canales DAC, en principio sólo queremos reproducir el sonido por uno de ellos, aunque sólo sea por simplificar el montaje electrónico necesario, así que mezclaremos ambos canales en Audacity con «Pistas - Mezclar - Mezclar pista estéreo a mono». Eso dejará la ventana de Audacity como se ve en la siguiente imagen: Ahora, y para que podamos oír el audio con el mejor volumen posible, amplificamos la muestra con «Seleccionar - Todo» y «Efecto - Amplificar». Dejando los valores que Audacity propone por defecto obtendremos un sonido de más amplitud sin distorsiones. Con «Aceptar», veremos cómo la onda aumenta su amplitud: Sin embargo, todavía nos queda un poco más de trabajo con este trocito de audio. En primer lugar, debemos cambiar la frecuencia de muestreo de 44100Hz a 16000Hz, cosa que se puede hacer fácilmente con el selector de abajo a la izquierda.
Finalmente, grabaremos el audio en un wav con «Archivo - Exportar - Exportar como WAV», especificando en la codificación «Unsigned 8 bits PCM». (Como hemos visto antes, los canales DAC del ESP32 son de 8 bits, por lo tanto necesitamos que los datos que le enviemos también lo sean. Esto irá en detrimento de la calidad del sonido, pero es lo que hay) Como no es necesario añadir metadatos al archivo, basta con aceptar y ya tendremos nuestro archivo «prueba.wav» en la carpeta que hayamos elegido. En mi caso, el tamaño del archivo es de 206KB, lo que no es excesivo, pero hay que tener en cuenta que sólo son unos segundos.
Convertir archivo wav a array de datos
Ahora necesitamos convertir ese archivo de audio (en mi caso, «prueba.wav») a una matriz de datos que el ESP32 sea capaz de leer. Para ello utilizaré el programa «ImHex», disponible en los repositorios de Linux Mint, aunque hay otros tanto para Linux como para Windows que pueden realizar el mismo trabajo. Con «File - Open file» seleccionaremos nuestra muestra de audio. Eso hará que la ventana del programa aparezca como en la siguiente imagen: Pinchamos sobre el primer dato hexadecimal, y luego seleccionamos en el menú «Edit - Select all». Después de esto, seleccionaremos «Edit - Copy as - C array», como en la imagen: Con esto tendremos en nuestro portapapeles los datos de la muestra de audio como un array de datos formateado para el lenguaje C usado por la ESP32. Es hora de abrir el IDE de Arduino.
Preparar el archivo auxiliar de datos de sonido
Abierto el IDE de Arduino, y configurado para trabajar con la ESP32, debemos crear una nueva pestaña donde poner los datos de sonido, como se ve en la imagen. A esa pestaña le podemos llamar «audio.h» Ahora podemos pegar ahí nuestros datos copiados desde ImHex, ya sea con «Editar - Pegar» o con «CTRL+V». Esto nos dejará más de 12.000 líneas de datos en el archivo, que ahora aparecerá más o menos así: Ahora ya tenemos nuestros datos listos para ser invocados por el programa que reproducirá el sonido. Este programa utilizará la librería XT_DAC_Audio, que vamos a describir a continuación.
Librería XT_DAC_Audio, breve descripción
La librería XT_DAC_Audio se puede descargar desde la página de su autor (https://www.xtronical.com/the-dacaudio-library-download-and-installation/) e instalar con el gestor de librerías en nuestro IDE de Arduino, de manera que podamos invocarla fácilmente en cualquiera de nuestros proyectos.
XT_DAC_Audio es una librería bastante potente, que puede leer gran multitud de cadenas de audio y reproducirlas de diferentes maneras, cambiando su velocidad, volumen, generando listas de reproducción, etc. Veremos brevemente algunas de estas características.
Programa de ejemplo
Las dos primeras líneas de nuestro programa de ejemplo invocarán a las librerías necesarias:
#include "audio.h" #include <XT_DAC_Audio.h>
Ahora creamos dos objetos, uno de la clase XT_Wav_Class (hey_jude), y otro de la clase TX_DAC_Audio (DacAudio). El primero contendrá los datos de la matriz data, que se encuentra en “audio.h”, y el segundo manejará la salida de sonido a través del primer canal DAC del ESP32, que se encuentra en el pin 25, y se le asigna el timer 0 (aunque esto de momento no nos incumbe para realizar este ejemplo).
XT_Wav_Class hey_jude(data); XT_DAC_Audio_Class DacAudio(25,0);
A continuación, en la función setup() he cambiado una propiedad del objeto hey_jude que le indica la velocidad a la que debe reproducirse este audio. Es una propiedad que podemos cambiar en el momento que queramos, incluso dentro del ciclo de reproducción del mismo.
void setup() { hey_jude.Speed = 1; }
Finalmente, en la función loop() (que es la que se repite de forma continua en el ESP32), lo primero que ordenamos al objeto que gestiona el sonido es que llene su buffer de datos, una memoria intermedia entre nuestros datos y el exterior que garantiza la estabilidad del sonido que se va a producir. Lo hacemos en cada inicio del bucle, de manera que este buffer esté siempre lleno.
Lo siguiente es un condicional para saber si nuestro sonido se está reproduciendo o no, y si no se está reproduciendo, se utiliza el método «Play» del objeto DacAudio para que reproduzca el contenido del objeto hey_jude. De esta forma, el audio se reproducirá una y otra vez, sin parar.
void loop() { DacAudio.FillBuffer(); if(hey_jude.Playing==false) DacAudio.Play(&hey_jude); }
El sketch completo quedaría así:
#include "audio.h" #include <XT_DAC_Audio.h> XT_Wav_Class hey_jude(data); XT_DAC_Audio_Class DacAudio(25,0); void setup() { hey_jude.Speed = 1; } void loop() { DacAudio.FillBuffer(); if(hey_jude.Playing==false) DacAudio.Play(&hey_jude); }
Esto, claro, es un ejemplo básico de cómo reproducir sonido con el ESP32, pero como he comentado, la librería XT_DAC_Audio es mucho más potente, y os invito a estudiarla para sacar todo el provecho posible al sonido de vuestros microcontroladores ESP32.