Java: leer, manipular y escribir archivos WAV

En un progtwig Java, ¿cuál es la mejor manera de leer un archivo de audio (archivo WAV ) en una matriz de números ( float[] , short[] , …) y escribir un archivo WAV a partir de una matriz de números?

Un poco más de detalle sobre lo que le gustaría lograr sería útil. Si los datos RAW en bruto son correctos para usted, simplemente use un FileInputStream y probablemente un Escáner para convertirlo en números. Pero déjame intentar darte un código de muestra significativo para que comiences:

Hay una clase llamada com.sun.media.sound.WaveFileWriter para este propósito.

 InputStream in = ...; OutputStream out = ...; AudioInputStream in = AudioSystem.getAudioInputStream(in); WaveFileWriter writer = new WaveFileWriter(); writer.write(in, AudioFileFormat.Type.WAVE, outStream); 

Podrías implementar tu propio AudioInputStream que haga lo que sea para convertir tus matrices de números en datos de audio.

 writer.write(new VoodooAudioInputStream(numbers), AudioFileFormat.Type.WAVE, outStream); 

Como mencionó @stacker , debes familiarizarte con la API, por supuesto.

Leo archivos WAV a través de AudioInputStream . El siguiente fragmento de los Tutoriales de Java Sound funciona bien.

 int totalFramesRead = 0; File fileIn = new File(somePathName); // somePathName is a pre-existing string whose value was // based on a user selection. try { AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(fileIn); int bytesPerFrame = audioInputStream.getFormat().getFrameSize(); if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) { // some audio formats may have unspecified frame size // in that case we may read any amount of bytes bytesPerFrame = 1; } // Set an arbitrary buffer size of 1024 frames. int numBytes = 1024 * bytesPerFrame; byte[] audioBytes = new byte[numBytes]; try { int numBytesRead = 0; int numFramesRead = 0; // Try to read numBytes bytes from the file. while ((numBytesRead = audioInputStream.read(audioBytes)) != -1) { // Calculate the number of frames actually read. numFramesRead = numBytesRead / bytesPerFrame; totalFramesRead += numFramesRead; // Here, do something useful with the audio data that's // now in the audioBytes array... } } catch (Exception ex) { // Handle the error... } } catch (Exception e) { // Handle the error... } 

Para escribir un WAV, encontré eso bastante complicado. En la superficie, parece un problema circular, el comando que escribe depende de un AudioInputStream como parámetro.

Pero, ¿cómo se escriben los bytes en un AudioInputStream ? ¿No debería haber un AudioOutputStream ?

Lo que encontré fue que uno puede definir un objeto que tiene acceso a los datos de bytes de audio en bruto para implementar TargetDataLine .

Esto requiere la implementación de muchos métodos, pero la mayoría puede permanecer en forma ficticia ya que no son necesarios para escribir datos en un archivo. El método clave para implementar es read(byte[] buffer, int bufferoffset, int numberofbytestoread) .

Como este método probablemente se invoque varias veces, también debe haber una variable de instancia que indique qué tan lejos avanzaron los datos, y actualizar eso como parte del método de read anterior.

Cuando haya implementado este método, su objeto se puede usar para crear un nuevo AudioInputStream que a su vez se puede usar con:

 AudioSystem.write(yourAudioInputStream, AudioFileFormat.WAV, yourFileDestination) 

Como recordatorio, se puede crear un TargetDataLine con una TargetDataLine como fuente.

En cuanto a la manipulación directa de los datos, he tenido éxito al actuar sobre los datos en el buffer en el bucle más interno del ejemplo anterior de snippet, audioBytes .

Mientras se encuentra en ese bucle interno, puede convertir los bytes en enteros o flotantes y multiplicar un valor de volume (que va de 0.0 a 1.0 ) y luego convertirlos de nuevo a bytes pequeños de endian.

Creo que, dado que tiene acceso a una serie de muestras en ese búfer, también puede utilizar diversas formas de algoritmos de filtrado DSP en esa etapa. En mi experiencia, he descubierto que es mejor hacer cambios de volumen directamente en los datos en este búfer porque entonces puede hacer el incremento más pequeño posible: un delta por muestra, minimizando la posibilidad de clics debido a discontinuidades inducidas por el volumen.

Encuentro que las “líneas de control” para el volumen proporcionado por Java tienden a situaciones donde los saltos en el volumen causarán clics, y creo que esto se debe a que los deltas solo se implementan en la granularidad de una sola lectura del búfer (a menudo en el rango de uno cambio por 1024 muestras) en lugar de dividir el cambio en piezas más pequeñas y agregarlas una por muestra. Pero no estoy al tanto de cómo se implementaron los Controles de Volumen, así que por favor tome esa conjetura con un grano de sal.

En general, Java.Sound ha sido un verdadero dolor de cabeza para descubrir. Me falla el tutorial por no incluir un ejemplo explícito de escribir un archivo directamente desde bytes. Me falla el tutorial para enterrar el mejor ejemplo de la encoding Reproducir un archivo en la sección “Cómo convertir …”. Sin embargo, hay MUCHA información valiosa GRATIS en ese tutorial.


EDITAR: 13/12/17

Desde entonces, he usado el siguiente código para escribir audio desde un archivo PCM en mis propios proyectos. En lugar de implementar TargetDataLine se puede extender InputStream y usarlo como un parámetro para el método AudioInputStream.write .

 public class StereoPcmInputStream extends InputStream { private float[] dataFrames; private int framesCounter; private int cursor; private int[] pcmOut = new int[2]; private int[] frameBytes = new int[4]; private int idx; private int framesToRead; public void setDataFrames(float[] dataFrames) { this.dataFrames = dataFrames; framesToRead = dataFrames.length / 2; } @Override public int read() throws IOException { while(available() > 0) { idx &= 3; if (idx == 0) // set up next frame's worth of data { framesCounter++; // count elapsing frames // scale to 16 bits pcmOut[0] = (int)(dataFrames[cursor++] * Short.MAX_VALUE); pcmOut[1] = (int)(dataFrames[cursor++] * Short.MAX_VALUE); // output as unsigned bytes, in range [0..255] frameBytes[0] = (char)pcmOut[0]; frameBytes[1] = (char)(pcmOut[0] >> 8); frameBytes[2] = (char)pcmOut[1]; frameBytes[3] = (char)(pcmOut[1] >> 8); } return frameBytes[idx++]; } return -1; } @Override public int available() { // NOTE: not concurrency safe. // 1st half of sum: there are 4 reads available per frame to be read // 2nd half of sum: the # of bytes of the current frame that remain to be read return 4 * ((framesToRead - 1) - framesCounter) + (4 - (idx % 4)); } @Override public void reset() { cursor = 0; framesCounter = 0; idx = 0; } @Override public void close() { System.out.println( "StereoPcmInputStream stopped after reading frames:" + framesCounter); } } 

La fuente de datos que se exportará aquí es en forma de flotadores estéreo que van de -1 a 1. El formato de la secuencia resultante es de 16 bits, estéreo, little-endian.

markSupported skip y markSupported métodos markSupported para mi aplicación particular. Pero no debería ser difícil agregarlos si son necesarios.

El paquete javax.sound.sample no es adecuado para procesar archivos WAV si necesita tener acceso a los valores de muestra reales. El paquete le permite cambiar el volumen, la frecuencia de muestreo, etc., pero si desea otros efectos (por ejemplo, agregar un eco), usted está solo. (El tutorial de Java insinúa que debería ser posible procesar los valores de muestra directamente, pero el escritor técnico prometió demasiado).

Este sitio tiene una clase simple para procesar archivos WAV: http://www.labbookpages.co.uk/audio/javaWavFiles.html

Especificación de archivo WAV https://ccrma.stanford.edu/courses/422/projects/WaveFormat/

Hay una API para su propósito http://code.google.com/p/musicg/

Este es el código fuente para escribir directamente en un archivo wav. Solo necesita saber las matemáticas y la ingeniería de sonido para producir el sonido que desea. En este ejemplo, la ecuación calcula un ritmo binaural.

 import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; public class Example { public static void main(String[] args) throws IOException { double sampleRate = 44100.0; double frequency = 440; double frequency2 = 90; double amplitude = 1.0; double seconds = 2.0; double twoPiF = 2 * Math.PI * frequency; double piF = Math.PI * frequency2; float[] buffer = new float[(int) (seconds * sampleRate)]; for (int sample = 0; sample < buffer.length; sample++) { double time = sample / sampleRate; buffer[sample] = (float) (amplitude * Math.cos((double)piF *time)* Math.sin(twoPiF * time)); } final byte[] byteBuffer = new byte[buffer.length * 2]; int bufferIndex = 0; for (int i = 0; i < byteBuffer.length; i++) { final int x = (int) (buffer[bufferIndex++] * 32767.0); byteBuffer[i] = (byte) x; i++; byteBuffer[i] = (byte) (x >>> 8); } File out = new File("out10.wav"); boolean bigEndian = false; boolean signed = true; int bits = 16; int channels = 1; AudioFormat format; format = new AudioFormat((float)sampleRate, bits, channels, signed, bigEndian); ByteArrayInputStream bais = new ByteArrayInputStream(byteBuffer); AudioInputStream audioInputStream; audioInputStream = new AudioInputStream(bais, format,buffer.length); AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, out); audioInputStream.close(); } } 

Si pudieras modificar esto para crear un subgraves de hip hop, sería fantástico porque eso es lo que bash modificar este progtwig en la actualidad.

En primer lugar, es posible que necesite conocer los encabezados y las posiciones de datos de una estructura WAVE, aquí puede encontrar las especificaciones. Tenga en cuenta que los datos son poco endian.

Hay una API que puede ayudarte a lograr tu objective.

Los archivos Wave son compatibles con el paquete javax.sound.sample

Como no es una API trivial, debes leer un artículo / tutorial que introduce la API como

Java Sound, una introducción

Yo uso FileInputStream con algo de magia:

  byte[] byteInput = new byte[(int)file.length() - 44]; short[] input = new short[(int)(byteInput.length / 2f)]; try{ FileInputStream fis = new FileInputStream(file); fis.read(byteInput, 44, byteInput.length - 45); ByteBuffer.wrap(byteInput).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(input); }catch(Exception e ){ e.printStackTrace(); } 

¡Sus valores de muestra están en short[] input !