Jueves, 3 de enero del 2008
SDL_mixer vs PortAudio, el desenlace
Desde la última entrada en el blog, hemos estado un par de días meditando sobre la implementación definitiva del sistema de sonido a emplear en Project Football, y tras haber examinado las dos opciones que habíamos planteado, hemos seleccionado la opción que nos parece mas apropiada.
Los requisitos de sonido en Project Football son bastante sencillos. Por un lado queremos reproducir música (una canción a la vez) y por otro lado efectos sonoros (pueden ser varios simultáneamente), cosa que es típica de cualquier juego de hoy en día. En base a eso, vamos a ver que nos proporciona PortAudio y SDL_mixer.
Los requisitos de sonido en Project Football son bastante sencillos. Por un lado queremos reproducir música (una canción a la vez) y por otro lado efectos sonoros (pueden ser varios simultáneamente), cosa que es típica de cualquier juego de hoy en día. En base a eso, vamos a ver que nos proporciona PortAudio y SDL_mixer.
En primer lugar vamos a analizar PortAudio. Se trata de una biblioteca multiplataforma escrita en "C", preparada para realizar streaming tanto de entrada como salida de audio y para utilizarla solo hay que implementar una función callback, inicializar la biblioteca y abrir un flujo de entrada/salida asociándolo con la función callback que hemos creado. A partir de ese momento, cada vez que PortAudio tenga/requiera mas datos de audio para procesar, realizará una llamada a dicha función, pudiendo leer o escribir datos de audio desde la misma. Esto presenta una serie de ventajas e inconvenientes con respecto a OpenAL.
Ventajas:
Inconvenientes:
Vamos ahora con SDL_mixer. De nuevo se trata de una biblioteca multiplataforma escrita en "C", pero en esta ocasión está orientada completamente al mundo de los videojuegos (como el resto de bibliotecas de la familia SDL). En este caso, la biblioteca hace distinción entre lo que son los efectos sonoros o samples y la música, soportando para cada caso formatos de audio distintos y haciendo un tratamiento distinto en cada caso. Mientras que con los samples, SDL_mixer soporta directamente los formatos WAVE, AIFF, RIFF, OGG, y VOC, para música soporta WAVE, MOD, MIDI, OGG, y MP3, aplicando además, en el caso de la música, técnicas de streaming de forma transparente para el programador, de forma que la programación se hace bastante rápida y fácil.
Ventajas:
Inconvenientes:
Por todo esto, finalmente nos hemos inclinado por SDL_mixer. Principalmente, por la facilidad para integrarlo e implementarlo en el proyecto y por el soporte para múltiples formatos de audio, reduciendo considerablemente el número de dependencias. Y aunque, como hemos comentado, tiene la limitación para añadir soporte a más formatos de audio, los que trae por defecto consideramos que son suficientes y abarcan los más usados, incluyendo tanto para samples como para música el formato ogg.
Ya está implementado el sistema de audio sobre SDL_mixer, funciona correctamente, e irá incorporado en la próxima release de Project Football.
Se aceptan sugerencias, comentarios y críticas.
Saludos. d_b
Ventajas:
- Función callback que va procesando los datos conforme se va necesitando (OpenAL no soportaba esto y había que consultar periódicamente para ver si había que introducir más datos).
- Flexibilidad a la hora de implementar la reproducción de distintos formatos de audio al no estar atado a ningún formato en particular (esto tiene más sentido cuando veamos SDL_mixer).
Inconvenientes:
- Sin soporte previo para ningún formato determinado. Es decir, si quieres que tu aplicación sea capaz de reproducir mp3 o ogg (vorbis), tienes que implementarlo tu mismo en la función callback.
- Ciertas limitaciones con el número de flujos que se pueden abrir en función del sistema en el que se encuentre la biblioteca
Vamos ahora con SDL_mixer. De nuevo se trata de una biblioteca multiplataforma escrita en "C", pero en esta ocasión está orientada completamente al mundo de los videojuegos (como el resto de bibliotecas de la familia SDL). En este caso, la biblioteca hace distinción entre lo que son los efectos sonoros o samples y la música, soportando para cada caso formatos de audio distintos y haciendo un tratamiento distinto en cada caso. Mientras que con los samples, SDL_mixer soporta directamente los formatos WAVE, AIFF, RIFF, OGG, y VOC, para música soporta WAVE, MOD, MIDI, OGG, y MP3, aplicando además, en el caso de la música, técnicas de streaming de forma transparente para el programador, de forma que la programación se hace bastante rápida y fácil.
Ventajas:
- Streaming para de la música de forma transparente para el programador.
- Soporte para varios formatos de audio (mp3, ogg, etc.) directamente por la biblioteca.
- Poca programación por parte del programador.
Inconvenientes:
- Imposibilidad de dar soporte para más formatos. Por ejemplo, en el caso que se quiera cargar un sample en formato mp3 habría que esperar a que la biblioteca diese soporte
- Solo se puede reproducir un archivo de música a la vez. Aunque esto no es demasiado inconveniente, ya que no tiene mucho sentido querer reproducir más de un archivo de música a la vez.
- Relacionado con lo anterior, no es posible consultar si se está reproduciendo un archivo de música concreto u otro, ya que el solo permite consultar si se está o no reproduciendo música. Deberá ser el programador el que se encargue de memorizar que canción se está reproduciendo en cada momento (siempre que sea útil para la aplicación concreta, ya que en principio no es necesario)
Por todo esto, finalmente nos hemos inclinado por SDL_mixer. Principalmente, por la facilidad para integrarlo e implementarlo en el proyecto y por el soporte para múltiples formatos de audio, reduciendo considerablemente el número de dependencias. Y aunque, como hemos comentado, tiene la limitación para añadir soporte a más formatos de audio, los que trae por defecto consideramos que son suficientes y abarcan los más usados, incluyendo tanto para samples como para música el formato ogg.
Ya está implementado el sistema de audio sobre SDL_mixer, funciona correctamente, e irá incorporado en la próxima release de Project Football.
Se aceptan sugerencias, comentarios y críticas.
Saludos. d_b


También estoy pensándome colgar un tutorial de integración con Eclipse, os molestaría que tomara vuestro post como referencia?
Estaba pensando precisamente en escribir alguna referencia similar sobre como configurar Eclipse + Ogre + Cegui en Windows, puesto que el otro día anduve cacharreando con este tema para crear un binario del proyecto en Windows.
Y me he encontrado con su blog, y me he quedado pegado tambien viendo como han podido salir del paso.
Excelente aporte y muy bien redactado!! y sigo leyendo su blog
Saludos!
Gracias por comentar. Un saludo.
Tengo un fichero de audio en el que por un canal hay audio (que tengo que reproducir) y por el otro hay tonos de vez en cuando. Los tonos no los tengo que reproducir, lo que tengo que hacer es detectar cuando se producen para realizar acciones en mi programa.
Cualquier ayuda es mas que bienvenida
Lo único que se me ocurre sería que separaras los dos canales en dos ficheros de audio distinto y reproducirlos a la vez (el de los tonos sin volumen), pero eso no elimina el problema de que sigues sin poder acceder a los datos de audio que irían a la tarjeta de sonido para poder detectar los pulsos (aparte que con SDL_Mixer no se puede controlar el volumen de un canal concreto). ¿No te resultaría mas sencillo llevar el control de esos pulsos de otra forma, como por ejemplo un script que indique cuando ocurren?
Un saludo, y espero haberte ayudado un poco aunque sea.
Estoy viendo una manera de hacerlo con .NET aunque de momento me temo que me queda mucho por andar ya que es complicado.
De todas maneras me vale cualquier solucion.
Dime por favor, ¿como se hace con un script?
Agradezco mucho tu ayuda
En base a eso lo que se me había ocurrido sería algo así como esto:
Tu tienes tu sonido por un lado, llamémosle sonido_1.mp3 , y por otro el script que puede ser simplemente un txt al que llamaremos sonido_1.txt. El contenido de ese fichero de texto podría ser algo como esto:
00:00:15.3 ACCION_1
00:01:03.0 ACCION_2
00:02:35.9 ACCION_3
Que vendría a significar algo así como:
- "Cuando lleve 15 segundo y 3 décimas reproducidos, realiza la acción ACCION_1"
- "Cuando lleve 1 minuto y 3 segundos reproducidos, realiza la acción ACCION_2"
- "Cuando lleve 2 minutos, 35 segundos y 9 décimas reproducidos realiza la acción ACCION_3"
Parsear ese fichero no sería complicado y simplemente lo que tendrías que hacer sería lo siguiente:
(a)- Parsear los datos del fichero txt (tiempo de ejecución y acción a realizar) y los almacenas de alguna forma (por ejemplo en una lista).
(b)- Tomar un tiempo de referencia usando el método SDL_GetTicks.
(c)- Inicializar una estructura con la lista de acciones, el numero de acciones de la lista, el tiempo de referencia, y otro entero que nos servirá para saber cuando fue la última vez que se realizó la llamada y que pondremos a cero.
(d)- Crear un temporizador usando el método SDL_AddTimer. Al crear el temporizador establecer el intervalo entre llamadas en 100 milisegundos y pasarle la estructura creada en el paso anterior como parámetro.
(e)- Reproducir el sonido bien con Mix_PlayChannel o Mix_PlayMusic, en función de si se trata de un efecto de sonido o música respectivamente.
Esto a partir de ese momento, SDL llamará a la función de callback que le hayas definido en el temporizador cada 100 ms. Dentro de esta función deberías hacer lo siguiente:
(1)- Comprobar si el sonido se sigue reproduciendo mediante lo métodos Mix_Playing o Mix_PlayingMusic. En caso que no sea así devolver 0, cancelando así el temporizador.
(2)- Si el sonido se está reproduciendo, comprobar cuanto tiempo ha transcurrido desde que se inició la reproducción mediante una llamada a SDL_GetTicks y restándole el tiempo de referencia inicial (paso (b)).
(3)- Comprobar para cada una de las acciones de la lista, si el tiempo en el que la acción se debe ejecutar está entre el tiempo de la llamada anterior (el que está en la estructura, paso (c)) y esta llamada (el tiempo calculado en el paso (2)), si es así entonces realizar la acción indicada.
(4)- Actualizar el tiempo de la llamada anterior que está almacenado en la estructura con el tiempo calculado para esta llamada.
(5)- Devolver el valor recibido por el primer parámetro de la función de callback.
NOTA: Cuando pongo dos posibles métodos del SDL_Mixer a invocar, lo hago porque tienes que llamar a uno u otro en función de si el sonido es un sample o es música respectivamente.
NOTA2: Si el sonido lo puedes pausar debes tenerlo en cuenta a la hora de calcular el tiempo transcurrido en el paso (2).
NOTA3: Si quieres aumentar la precisión deberás reducir el intervalo de tiempo entre llamadas a la función callback en el paso (d).
NOTA4: Si utilizas memoria dinámica para la estructura, recuerda liberarla al cancelar el temporizador.
Referencia:
http://www.libsdl.org/cgi/docwiki.cgi/SDL_Time
http://jcatki.no-ip.org:8080/SDL_mixer/SDL_mixer_frame.html
Un saludo y espero que esto sea lo que buscabas.
Sin más, un saludo.
La solucion que das es buena. Es asi como lo pensaba hacer en un principio. El problema surgio cuando vi que se trataba de miles de acciones.
Entre el tiempo que me iba a llevar cronometrar las acciones y crear el archivo de texto mas me valia encontrar la manera de pillar los picos de señal.
Finalmente lo voy a hacer con .NET ya que hay por lo menos una manera de acceder a los samples y poder detectar picos en el canal que me interesa. Por si a alguien le hiciera falta o simplemente quiera cacharrear estos son los enlaces que me han convencido:
ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.WIN32COM.v10.en/multimed/htm/_win32_devices_and_data_types.htm
ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.MOBEMBDEV.v10.en/dnnetcomp/html/WaveInOut.htm
(ambos son enlaces de la ayuda de visual studio)
Mi unico problema ahora es utilizar SDL con visual studio. Espero poder utilizar sin problemas las cabeceras y dll's. Digo espero pq no he visto ningun manual de SDL en visual studio.
Insisto, muchas gracias por tu ayuda d_b y un saludo