Sonido en JAVA.
Escrito por:
Escrito por:
Carlos Prades del Valle.
Carlos Prades del Valle.
Versión 1.0.0.
Versión 1.0.0.
Enero de 2001.
Historial del documento.
Versión Autor Resumen de la modificación. Fecha
1.0.0. CPV Guía para el programador de sonido con Java. 19−12−2000 1.0.1. CPV Revisión de estilos para subirlo a la web. 31−01−2001
Autores del documento.
CPV: Carlos Prades del Valle. e−mail: [email protected]
Sitio web: http://cprades.eresmas.com/
Resumen.
Este documento es una guía para el programador que quiere empezar a utilizar el sonido que le proporciona el sistema multimedia que utiliza.
Palabras relacionadas.
Índice.
1INTRODUCCIÓN. ___________________________________________________________7 2CLASES PRINCIPALES. _____________________________________________________8 2.1FORMATOS DE AUDIO.________________________________________________________8
2.2ARQUITECTURA DE LOS SISTEMAS.________________________________________________9
2.3MIXERS__________________________________________________________________9
2.4LINES__________________________________________________________________10
2.5CLASES DATALINE_________________________________________________________11
3ACCESO A LOS COMPONENTES DEL SISTEMA _____________________________12 3.1OBTENCIÓN DE LOS RECURSOS._________________________________________________13
4REPRODUCCIÓN DE SONIDO. ______________________________________________15 4.1REPRODUCCIÓN DEL SONIDO MEDIANTE SOURCEDATALINE_____________________________15
4.2SINCRONIZACIÓN DE LÍNEAS.__________________________________________________17
4.3CAPTURA DE SONIDO._______________________________________________________17
Glosario.
API: Application Program Interface. Es la interfaz proporcionada por un sistema al programador para poder acceder a los servicios de ese sistema.
Array: Es un grupo de datos de un tipo determinado puestos uno a continuación de otro. Coincide con los tipos de datos array de Java.
Java: Lenguaje de programación orientado a objetos.
JDK: Java Development Kit. Es el entorno de desarrollo para Java proporcionado por Sun.
MIDI: Estándar para el almacenamiento y transporte de música para o desde un sintetizador.
Package: Paquete. Agrupamiento especificado por el programador de clases con características comunes.
Ámbito y alcance del documento.
Este documento pretende mostrar cómo utilizar las herramientas del API de Java para la adquisición el manejo y la reproducción de sonido, el texto se limita a esta función quedando fuera del propósito el almacenamiento, la síntesis de sonido, el tratamiento de secuencias MIDI, el tratamiento (reconocimiento y síntesis) de la señal de voz, etc. No es una descripción detallada de todos las clases, métodos y variables que proporciona el sistema sino una guía útil para el programador que pretenda iniciarse en el procesado de sonido con Java o cómo texto de referencia para el programador más experto.
La versión de Java que soporta este API como aquí se cuenta es la 2 y el entono de desarrollo utilizado es el JDK 1.3.
Convenciones del documento.
En este documento se intentará traducir los términos en inglés siempre que sea posible, exceptuando aquellos términos en inglés que, por el uso común en nuestro idioma, no necesitan tal traducción. Estos vocablos están escritos en cursiva. Por otro lado los nombres propios de las compañías comerciales o de las marcas de sus productos también son puestos en cursiva.
1 Introducción.
El API de Java dedicada al sonido es la llamada Java Sound API proporcionado ya con el entorno de desarrollo de Sun. Este API se compone de 4 packages (paquetes):
javax.sound.sampled. javax.sound.sampled.spi. javax.sound.midi.
javax.sound.midi.spi.
2 Clases principales.
Como paso previo es necesario contar cómo funcionan algunos de los objetos de javax.sound.sampled necesarios para comprender después cada uno de los procedimientos a seguir.
2.1 Formatos de audio.
Los objetos de la clase AudioFormat definen el formato de una señal de audio, esto es, el número de canales, el número de bits por muestra, frecuencia de muestreo, el tamaño de la trama de voz y su frecuencia, formato de almacenamiento de los datos en memoria (big endian o little endian) y el tipo de codificación (ley a, ley µ, PCM). Para guardar el tipo de codificación hay un objeto de la clase
AudioFormat.Encoding con esta información. Obteniendo este objeto se puede mirar si es igual (función boolean equals(Object) ) a una de las constantes definidas en la clase AudioFormat.Encoding (ALAW, ULAW, PCM_SIGNED,
PCM_UNSIGNED). La clase AudioFormat aparte del constructor, al que hay que pasarle los parámetros para la iniciación de las variables, tiene los siguientes métodos:
int getChanels(); /* Devuelve el número de canales. */
AudioFormat.Encoding getEncoding(); /* Devuelve objeto con información sobre el tipo de codificación.*/
float getFrameRate(); /* Devuelve la frecuencia de trama de la señal. */
int getFrameSize(); /* Devuelve tamaño de la trama en bytes. */ float getSampleRate(); /* Devuelve la frecuencia de muestreo. */ int getSampleSizeInBits(); /* Devuelve el tamaño en bits de cada
muestra de sonido. */
boolean isBigEndian(); /* Devuelve true alineamiento big endian y false little endian. */
Además de los métodos boolean maches(AudioFormat) y String toString() y los heredados de la clase Object.
Por otro lado existe la claseAudioFileFormatpara definir el formato de un fichero de audio, esta clase permite utilizar las clases AudioImputStream y
AudioOutputStream, que heredan de ImputStream y OutputStream
2.2 Arquitectura de los sistemas.
La arquitectura de los sistemas de audio de audio se basa en mezcladores, clase
Mixer, que son dispositivos a los que llegan líneas, objetos de clase Line, procesan los datos y salen otros objetos de clase Line.
La jerarquía de estas clases es la siguiente:
Object Line Port Mixer DataLine SourceDataLine TargetDataLine Clip
2.3 Mixers
Como se puede ver losMixer(mezcladores) son unos casos particulares de los
Lines. Estos Mixer son dispositivos hardware o software por lo que pueden ser proporcionados por el sistema. Los objetos Mixer contienen un objeto de la clase
Mixer.Info con información del tipo de Mixer. Los métodos de la clase Mixer
son los siguientes:
Line getLine(Line.Info); /* Obtiene, si exixte, un Line del tipo indicado en el parámetro. */
int getMaxLines(Line.Info); /* Indica el numero de lineas que se pueden tener de un tipo dado.*/
Mixer.Info getMixerInfo(); /* Obtiene el objeto que indica el tipo de Mixer que es. */
Line.Info [] getSourceLineInfo(); /* Obtiene array con información de los SourceLines disponibles. */
Line.Info [] getSourceLineInfo(Line.Info); /* Array con información de los SourceLines de un tipo. */
Line [] getSourceLines(); /* Obtiene array con los SouceLines disponibles. */
Line.Info [] getTargetLineInfo(); /* Obtiene array con información de los TargetLines disponibles. */
Line.Info [] getTargetLineInfo(Line.Info); /* Array con información de los TargetLines de un tipo. */
Line [] getTargetLines(); /* Obtiene array con los TargetLines disponibles. */
boolean isLineSupported(Line.Info); /* Devuelve true si tiene un Line del tipo especificado. */
lineas especificadas en el primer parámetro, el segundo parámetro indica el tipo de sincronización: muestra a muestra si es true o sólo en los metodos start() y stop() si es false. */
void sincronize(Line[], boolean); /* Sincroniza las lineas especificadas en el primer parámetro, el segundo
parámetro indica el tipo de sincronización: muestra a muestra si es true o sólo en los metodos start() y stop() si es false. */ void unsincronize(Line[]); /* Desincroniza las lineas indicadas. */
2.4 Lines
Un Line (línea) es una conexión por la que pasa la señal desde o hacia un
Mixer. Los tipos de Lines son los siguientes:
Port: Puertos de entrada o salida del sistema como el micrófono, la salida de línea, el altavoz, etc.
DataLine: Líneas de datos, pueden ser Clips que almacenan un sonido completo, SourceDataLine que proporcionan un buffer de entrada a un Mixer y
TargetDataLine que proporcionan el buffer de salida de un Mixer.
Mixer: Mezclador que representa un dispositivo hardware o software del sistema.
Los Lines proporcionan cierta funcionalidad al sistema por medio de objetos Control que incluyan capaces de variar alguna característica del sonido (ganancia, reverberación, etc.), del estado (el Status) que puede ser abierto y cerrado (Open y Closed) de tal manera que si el Line está cerrado no consume recursos del sistema y por último por medio de los objetos Event (eventos) lanzados que permiten comunicación y sincronización con otros objetos.
Como ya veremos los objetosLine tienen un objeto (de claseLine.Info) de información sobre ellos. Como ocurre con la mayor parte de los atributos y métodos, el objeto de información se redefine en los objetos que lo heredan siendo de tipo
Mixer.Info, Port.Info, etc. en cada uno de los casos. Los métodos de la clase Line son:
Control getControl( Control.Type); /* Devuelve un objeto Control del tipo
especificado. */
Control [] getControls(); /* Devuelve un array con los objetos Control disponibles. */
Line.Info getLineInfo(); /* Devuelve información del objeto. */ boolean isControlSupported(Control.Type); /* Devuelve true si soporta el
tipo de Control especificado. */ boolena isOpen(); /* Devuelve true si el Status es Open. */
void open(); /* Abre (pasa a Status Open) el objeto Line.*/
void removeLineListener(LineListener); /* Deja de enviar objetos Event al LineListener especificado. */
2.5 Clases DataLine
La los objetos de la clase DataLine son las conexiones entre los Mixer y nuestro sistema. Estos objetos tienen un buffer de datos, un proceso de control interno, y una interfaz con el usuario. Como es lógico heredan todos los métodos de la clase
Line pero además implementan los siguientes:
int available(); /* Indica el número de bytes que que están libres en el buffer interno. */
void drain(); /* Este método sirve bloquea el objeto hasta que quede limpio el buffer. */
void flush(); // Este metodo limpia el buffer.
int getBufferSize(); // Devuelve el tamaño del buffer en bytes. AudioFormat getFormat(); // Devuelve el formato del audio manejado. int getFramePosition(); // Devuelve la posición de la trama
float getLevel(); // Devuelve el nivel de la señal
long getMicrosecondPosition(); /* Obtiene la posición actual de los datos de audio en microsegundos. */
boolean isActive(); /* Devuelve true si estan pasando datos (ejecutado el comando start) */
3 Acceso a los componentes del sistema
Para acceder a los componentes del sistema la clase AudioSystem
proporciona a la aplicación un punto de entrada a los componentes instalados en el sistema. Se puede obtener información de los objetosMixerinstalados y objetosLine
(sin tener que especificar el Mixer al que están asociados), proporciona métodos para realizar las conversiones de formato y métodos para trasladar el sonido a objetos
Stream o File para el transporte, comunicación o almacenamiento. Los métodos accesibles de esta clase son:
static AudioFileFormat getAudioFileFormat(java.io.File);
static AudioFileFormat getAudioFileFormat(java.io.InputStream); static AudioFileFormat getAudioFileFormat(java.net.URL);
/* Devuelven un objeto con el formato del fichero especificado en el parámetro. */
static AudioFileFormat.Type[] getAudioFileTypes();
static AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream); /* Devuelve los tipos de ficheros soportados por el sistema (sin parámetro) o por el parámetro. */
static AudioInputStream getAudioInputStream(AudioFormat, AudioInputStream );
static AudioInputStream getAudioInputStream(AudioFormat.Encoding, AudioInputStream);
// Devuelve un AudioImputStream
static AudioInputStream getAudioInputStream(java.io.File);
static AudioInputStream getAudioInputStream(java.io.InputStream); static AudioInputStream getAudioInputStream(java.net.URL);
// Devuelve un AudioInputStream asuciado a un fichero static Line getLine(Line.Info);
// Obtiene un objeto Line del tipo especificado en el parámetro. static Mixer getMixer(Mixer.Info);
// Obtiene un objeto Mixer del tipo especificado en el parámetro. static Mixer.Info[] getMixerInfo();
/* Obtiene un array con la información de los objetos Mixer existentes en el sistema. */
static Line.Info[] getSourceLineInfo(Line.Info p1);
/* Obtiene un array con la información de los objetos Line existentes en el sistema. */
static AudioFormat.Encoding[] getTargetEncodings(AudioFormat);
static AudioFormat.Encoding[] getTargetEncodings(AudioFormat.Encoding); /* Obtiene los tipos de codificación sopurtados para un determinado formato de audio. */
static AudioFormat[] getTargetFormats(AudioFormat.Encoding, AudioFormat); /* Obtiene los objetos AudioFormat de un determinado tipo. */
/* Obtiene información de los objetos TargetLine de un determinado tipo. */
static boolean isConversionSupported(AudioFormat, AudioFormat);
static boolean isConversionSupported(AudioFormat.Encoding, AudioFormat); /* Devuelve true si se permite la canversión entre los formatos especificados. */
static boolean isFileTypeSupported(AudioFileFormat.Type); static boolean isFileTypeSupported(AudioFileFormat.Type, AudioInputStream);
/* Devuelve true si se soporta el tipo de formato de fichero (para el AudioInputStream en el segundo caso). */
static boolean isLineSupported(Line.Info);
/* Devuelve true si en el sistema existe un objeto Line del tipo especificado. */
static int write(AudioInputStream, AudioFileFormat.Type, java.io.File); static int write(AudioInputStream, AudioFileFormat.Type,
java.io.OutputStream);
/* Graba los datos de audio en un fichero o en un OutputStream según el formato especificado. Devuelve el número de datos guardados. */
Para poder acceder a los distintos objetos del sistema se crean las clases de información, cuyas instancias proporcionan información sobre las distintas interfaces. Estas interfaces sonLine.Infoy las clases derivadasMixer.Info,Port.Infoy
DataLine.Info.
3.1 Obtención de los recursos.
Para obtener un objeto de la clase Mixer se puede conseguir un array de objetos Mixer.Info con todos los Mixer soportados por el sistema por medio del método getMixerInfo() de la clase AudioSystem. Con este array podemos elegir el Mixer que nos interese y obtenerlo con el método getMixer( Mixer.Info) de la clase AudioSystem.
Para obtener un objeto Line de un determinado tipo podemos obtenerlo de un
Mixer (ver apartado dedicado a los objetosMixer) o de la claseAudioSystemcon el método getLine(Line.Info). Se puede construir un objetoDataLine.Info
indicando en el constructor la clase del objeto del que informa (TargetDataLine.class oSourceDataLine.class) y un objeto de la clase
AudioFormat. Es conveniente ver si un objeto Linedel tipo deseado se soporta por el sistema, para ello se utiliza el método isLineSupported() de la clase
líneas básicas (Port.Info.COMPACT_DISC, Port.Info.HEADPHONE,
Port.Info.LINE_IN, Port.Info.LINE.OUT y Port.Info.SPEAKER). Para obtener un array con información de las líneas existentes en el sistema se usan las funciones getTargetLineInfo() y getSourcetLineInfo(), de la clase AudioSystem, pasándoles los tipos de líneas que nos interesan. Por otro lado, los objetos Mixer implementan los métodos getTargetLineInfo() y
getSourcetLineInfo() que no necesitan parámetros y que devuelven información sobre sus objetos Line. Con el métodogetLine(), del objeto Mixer, pasando como parámetro el Line.Info adecuado se obtiene la referencia al objeto
Line deseado.
Con estos datos es posible obtener un Line y manipularlo abriéndolo, cerrándolo, etc. Hay que advertir que no es aconsejable cambiar el Status de losPorts
4 Reproducción de sonido.
Antes de entrar en la reproducción de sonido por medio de objetos
SourceDataLine hay que hacer mención as los objetos Clip. Un objeto de esta clase está pensado para almacenar el audio grabado de principio a fin, es la mejor solución cuando se conoce de antemano el tamaño de la señal a almacenar y es única, así como cuando se desea repetir un sonido varias veces (por ejemplo en un lazo). Los ejemplo típico de utilización es la reproducción de sonido de ficheros no demasiado grandes, en este caso se lee el contenido del fichero en un clip y luego se reproduce, el control es más sencillo y los recursos utilizados son menores. Sin embargo en los casos donde el audio es continuo o en ficheros de un gran tamaño es preferible usar los otros tipos de
DataLine (SourceDataLine o TargetDataLine) con el fin de no ocupar demasiada memoria del sistema. En nuestro caso nos centraremos en este segundo caso ya que nuestra fin es la adquisición tratamiento y reproducción de la señal siéndonos de poca utilidad la clase Clip.
4.1 Reproducción del sonido mediante SourceDataLine
Los objetos SourceDataLine son la entrada de objetos Mixer, siendo necesario escribir en ellos los datos que se introducen en el Mixer. Los métodos de estos objetos son:
void open(AudioFormat); void open(AudioFormat, int);
// Redefinen el metodo de abir de Line pasando el formato de audio //que van a manejar y, opcionalmente, el tamaño en bytes del buffer. int write(byte [ ], int, int);
// Escribe los datos en el buffer (ver texto a continuación).
El proceso a realizar para la escritura de datos en el SourceDataLine una vez obtenido es el siguiente:
Se procede a abrirlo con el método propio open(AudioFormat) o
open(AudioFormat, int) donde el entero indica el tamaño del buffer en bytes, si no se utilizan argumentos se pone un formato por defecto. Para conocer estos datos se pueden utilizar los métodos getFormat() y getBufferSize() del objeto
SourceDataLine.
partir de donde, en el array b, se debe empezar a leer datos y length indica cuantos datos deben ser leídos. Esta función devuelve el número de datos leídos. Es muy importante tener en cuenta que todos estos datos vienen en bytes y no en número de muestras.
Cuando sale la primera muestra del objeto Mixer (un instante después de salir del SourceDataLine) se produce un evento de tipo STARTque puede ser recogido por el proceso a la salida del Mixer.
El métodowrite()vuelve sólo cuando ha escrito todos los datos en el buffer. Si se intenta escribir más datos de los que caben en el buffer el método se bloquea hasta terminar. Para evitarlo con el método available() se obtiene, en bytes, el tamaño de la parte que queda libre en el buffer.
Con el método drain() el programa se bloquea hasta reproducir el sonido, vaciando el buffer antes de volver. Con el método stop()se para la reproducción, sin limpiar el buffer, y constart()continúa dónde se quedó, para evitar la reproducción de un segmento antiguo al llamar a start() se puede utilizar el método flush()
que limpia el buffer. Todos estos métodos de SourceDataLine son heredados de
DataLine.
Cuando deja de salir señal del Mixer procedente del SourceDataLine se genera un evento de tipo STOP.
El método isActive() devuelve true si están saliendo datos (entre los eventos de tipo STARTySTOP), el método isRunning()devuelvetruesi la línea está abierta. Además Line genera eventos de tipo OPEN y CLOSE al llamarse a las funciones correspondientes. Todos los eventos pertenecen a la clase LineEvent y les debe de atender un objeto que implemente la interfaz LineListener. Para registrarlos se llama a la función de la clase Line, addLineListener(), pasando como parámetro el objeto LineListener.
La interfaz LineListener sólo define un método: void update(LineEvent). Los objetos LineEvent implementan los métodos:
final long getFramePosition(); // Obtiene la osicion de la trama
final Line getLine(); // Obtiene el objeto Line que lanzó el evento. final LineEvent.Type getType(); // Obtiene el tipo de evento
LineEvent(Line, LineEvent.Type, long); // Constructor
El tipo de evento es un objeto de clase LineEvent.Type, esta clase tiene definida las constantes LineEvent.Type.CLOSE, LineEvent.Type.OPEN,
LineEvent.Type.START y LineEvent.Type.STOP.
4.2 Sincronización de líneas.
La sincronización de líneas permite que la reproducción se realice al mismo tiempo, esta sincronización puede ser mantenida o no. La sincronización se llama mantenida cuando es una sincronización muestra a muestra, es decir, durante toda la reproducción las muestras de ambas líneas salen a la par. La sincronización no mantenida es aquella en la que sólo se sincronizan los procesos de start() y
stop().
Para ver si varios objetos Line se pueden sincronizar se utiliza el método, del
un objeto Mixer, isSinchronizationSupported()( Line [],
boolean) al que se le pasa un array con los objetos a sincronizar y unbooleanque indica si la sincronización es mantenida (valor true) o no (valor false), como es lógico devuelve true si se pueden sincronizar y false si no.
4.3 Captura de sonido.
Tras lo expuesto anteriormente en el apartado de reproducción vale con una pequeña referencia al apartado de captura de sonido ya que todo se hace prácticamente igual.
En la captura de audio losPorts (puertos) ponen datos en el Mixer y este en un objeto TargetDataLine que tiene los métodos:
void open(AudioFormat); void open(AudioFormat, int);
// Redefinen el metodo de abir de Line pasando el formato de audio que //van a manejar y, opcionalmente, el tamaño en bytes del buffer.
int read(byte [ ], int, int);
// Lee los datos desde el buffer (ver texto a continuación).
Aparte de los métodos heredados. Así se puede observar la cantidad de datos en el buffer con available() y leerlos con read().
Lo primero es obtener un objeto TargetDataLine (antes es necesario
DataLine.Info) y abrirlo con open(), indicando, si es necesario, el formato de audio (si no, pone uno por defecto) y el tamaño del buffer. Para leer los datos de
el array de bytesdonde dejar los datos, el segundo es el offseta partir del que se dejan los datos en el array y el tercero indica el número de datos a leer.
Para limpiar el buffer se utiliza el método drain().
Esta clase genera los mismos eventos que los que hemos visto en el caso anterior
LineEvent, con los mismos valores como LineEvent.Type (START, STOP,
OPEN y CLOSE).
4.4 Procesado de la señal.
Una vez capturada la señal y/o antes de la reproducción tenemos la señal en un array, momento que podemos aprovechar para el procesado de la señal, en el caso de un proceso que no sea en tiempo real este array puede ser almacenado para el procesado posterior o el procesado por parte de otro thread del programa.
Existe la posibilidad de usar los el procesado soportado por los Mixer o los
DataLine mediante el uso de los objetos de clase Control, esto permite utilizar funciones básicas de estos objetos como la ganancia (gain) o la reverberación (reverb). Todo objeto Line puede implementar este procesado. Los objetos Control en un
Mixer pueden afectar sólo a algunosLineasociados a él. La jerarquía de estas clases es: Control BooleanControl FloatControl EnumControl ComponentControl
Cada clase hija de Control tiene un una clase Type incluida que define constantes para cada tipo de control. Para obtener un Control de un Line se puede usar el método de ese Line getControl(Control.Type) que devuelve la instancia del Control, si queremos ver que controles soporta cada Line podemos llamar al método de Line getControls() que devuelve un array con todos los objetos Control soportados. Para saber que tipo de Control es el método del
Control getType() devuelve un objeto de clase Control.Type y con el método deObject getClass()se puede saber la clase derivada a la que pertenece el objeto. Para cambiar los parámetros de un objeto Control se utiliza el método
Bibliografía.
[1] Java Sound API Programmer’s Guide [ver 1.0]. Sun Microsystems, Inc. 1999−2000.
[2] Java Sound 1.0 API Specification. Sun Microsystems, Inc. http://java.sun.com/j2se/1.3/docs/guide/sound/.
[3] Java Sound Home Page. Diciembre 2000.