¿Alguna vez has pensado crear tu propia aplicación de reconocimiento de música como Shazam o Soundhound? Pues ahora puedes, gracias a un servicio de reconocimiento de música online llamado ACRCloud.
En mi caso te voy a enseñar a hacerlo en Android con lenguaje Kotlin, pero también podrías integrarlo en iOS o en cualquier otra plataforma gracias a su API Web.
Lo mejor es que te puedes registrar en el plan gratuito para hacer tus primeras pruebas. Tiene un límite de 100 peticiones al día, suficiente para poder poner en marcha tu proyecto.
Registro y creación del proyecto
El primer paso que debemos seguir es registrarnos en su web de modo que podamos acceder a la consola.
Una vez aquí, veremos los distintos servicios que ofrecen.
En nuestro caso nos interesa la parte de «Projects > Audio & Video recognition».
Aquí deberemos crear un nuevo proyecto pulsando «Create Project». Le asignamos un nombre y elegimos «Recorded audio», ya que vamos a reconocer la música que grabemos por el micrófono de nuestro dispositivo. Como «bucket» debemos elegir «ACRCloud Music». Opcionalmente podemos marcar «3rd party integration» si queremos que en los resultados de la música reconocida nos aporte enlaces de YouTube y Spotify, entre otros.
Una vez creado nos dará un «Access Key» y un «Access Secret». Ambos los utilizaremos en nuestro proyecto Android.
Descargar e integrar el SDK
El siguiente paso será descargar el SDK para Android desde el siguiente repositorio en GitHub.
Como veis, tenemos varios recursos: un proyecto de ejemplo en Java, un fichero de ejemplo en Kotlin, el código fuente de las librerías nativas, documentación y las propias librerías. Esto último es lo que nos interesa ahora mismo.
La carpeta «libs» está separada fundamentalmente en un SDK en Java (el fichero .jar) y código nativo en forma de librerías (ficheros .so) para las distintas arquitecturas.
Pues bien, vamos a crear un proyecto nuevo en blanco desde Android Studio.
El fichero .jar lo podemos mover ya a la carpeta «app/libs».
Para las liberías nativas, primero deberemos crear una carpeta llamada jniLibs dentro de app/src/main/. Después copiamos dentro las carpetas de las arquitecturas que queramos, quedando la siguiente estructura:
La parte de código Android
Ya tenemos preparado el SDK. Ahora a desarrollar nuestra aplicación.
Lo primero que debemos saber es que vamos a necesitar permisos de micrófono para grabar el audio y de Internet para comunicarnos con el Servidor de ACRCloud. De modo que en el archivo manifest añadimos lo siguiente:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
Como la grabación de audio es un permiso «peligroso», debemos solicitar acceso en tiempo de ejecución. Así que vamos a crear un botón en activity_main.xml para habilitar este permiso:
<Button android:id="@+id/permission" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enable mic" />
Podemos mostrarlo u ocultarlo en función de si disponemos o no de este permiso. Además, solicitar el permiso al pulsar el botón. Una forma rápida y sencilla de implementarlo sería con estas dos funciones:
fun checkPermission() { if (ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.RECORD_AUDIO) != 0) { permission.visibility = View.VISIBLE permission.setOnClickListener { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 100) } } else { permission.visibility = View.GONE hasPermission() } } fun hasPermission() {} override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 100) { checkPermission() } }
Ahora, en caso de que sí tenga permiso de micrófono habilitado podemos comenzar a reconocer el audio. Así que volvemos al layout y creamos un botón y un textView donde almacenaremos el resultado.
<TextView android:id="@+id/result" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="21sp" android:textStyle="bold" android:layout_margin="8dp"/> <Button android:id="@+id/recognize" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Recognize" />
Ahora podemos iniciar la configuración de ACRCloud copiando el siguiente código a nuestra Activity principal:
private var mClient: ACRCloudClient? = null fun initAcrcloud() { val config = ACRCloudConfig() config.acrcloudListener = this config.context = this // Please create project in "http://console.acrcloud.cn/service/avr". // Please create project in "http://console.acrcloud.cn/service/avr". config.host = "XXXXXX" config.accessKey = "XXXXXX" config.accessSecret = "XXXXXX" config.recorderConfig.rate = 8000 config.recorderConfig.channels = 1 mClient = ACRCloudClient() if (BuildConfig.DEBUG) { ACRCloudLogger.setLog(true) } mClient!!.initWithConfig(config) }
De momento vamos a dejar en texto plano las variables host, accessKey y accessSecret, pero deberías plantearte ocultar estos campos en producción. Mira algunos ejemplos para ocultar tus claves.
Para iniciar el reconocimiento de la canción al pulsar el botón podrías hacer algo así:
fun hasPermission() { recognize.setOnClickListener { startRecognition() } } fun startRecognition() { mClient?.let { if (it.startRecognize()) { result.text = "Recognizing..." } else { result.text = "Init error" } } ?: run { result.text = "Client not ready" } }
Ahora solo nos queda recibir el resultado. Para ello nuestra activity debe implementar la interfaz «IACRCloudListener». En el método «onResult()» recibiremos el callback de los resultados del reconocimiento. De modo que quedaría algo así:
class MainActivity : AppCompatActivity(), IACRCloudListener {
override fun onResult(acrResult: ACRCloudResult?) { acrResult?.let { Log.d("ACR", "acr cloud result received: ${it.result}") handleResult(it.result) } } override fun onVolumeChanged(vol: Double) { Log.d("ACR", "volume changed $vol") } fun handleResult(acrResult: String) {}
Si ejecutamos el programa veremos por consola que el resultado es un string en formato JSON. Es por ello que podemos realizar la transformación para poder leer los datos que queramos. Podemos ver un ejemplo del resultado del JSON en la siguiente página de documentación de ACRCloud.
Supongamos que solo nos interesa el título y el artista principal del primer resultado, que es además el más relevante. Para ello haríamos lo siguiente:
fun handleResult(acrResult: String) { var res = "" try { val json = JSONObject(acrResult) val status: JSONObject = json.getJSONObject("status") val code = status.getInt("code") if (code == 0) { val metadata: JSONObject = json.getJSONObject("metadata") if (metadata.has("music")) { val musics = metadata.getJSONArray("music") val tt = musics[0] as JSONObject val title = tt.getString("title") val artistt = tt.getJSONArray("artists") val art = artistt[0] as JSONObject val artist = art.getString("name") res = "$title ($artist)" } } else { // TODO: Handle error res = acrResult } } catch (e: JSONException) { res = "Error parsing metadata" Log.e("ACR", "JSONException", e) } result.text = res }
Solo nos queda liberar el cliente de ACRCloud al salir de la aplicación, en el método onDestroy:
override fun onDestroy() { super.onDestroy() mClient?.let { it.release() mClient = null } }
¡Y eso es todo! Con esto cuando pulsemos el botón nuestra app se pondrá a escuchar. Una vez tengamos el resultado del reconocimiento disponible se mostrará en el campo de texto.
Ahora solo nos quedaría mejorar nuestra app con un diseño único, algún indicador de progreso cuando está reconociendo la música, gestionar correctamente los errores de reconocimiento, la opción de cancelar un reconocimiento iniciado… Lo que se te ocurra para crear una app fantástica e inigualable.