In un programma Java, qual è il modo migliore per leggere un file audio (file WAV) in una matrice di numeri (float[]
, short[]
, ...) e scrivere un File WAV da una serie di numeri?Java - lettura, manipolazione e scrittura di file WAV
risposta
Onda sono supportati dal javax.sound.sample package
Dal momento che non è un banale API si dovrebbe leggere un articolo/tutorial che introduce l'API come
Qualche dettaglio in più su ciò che si' Mi piacerebbe ottenere sarebbe utile. Se i dati WAV grezzi vanno bene per te, usa semplicemente un FileInputStream e probabilmente uno Scanner per trasformarlo in numeri. Lasciatemi provare a fornirti un codice di esempio significativo per iniziare:
Esiste una classe chiamata com.sun.media.sound.WaveFileWriter per questo scopo.
InputStream in = ...;
OutputStream out = ...;
AudioInputStream in = AudioSystem.getAudioInputStream(in);
WaveFileWriter writer = new WaveFileWriter();
writer.write(in, AudioFileFormat.Type.WAVE, outStream);
Si potrebbe implementare il proprio AudioInputStream che fa tutto ciò voodoo per trasformare il vostro array numerici in dati audio.
writer.write(new VoodooAudioInputStream(numbers), AudioFileFormat.Type.WAVE, outStream);
Come @stacker accennato, si dovrebbe ottenere dimestichezza con le API di corso.
Il pacchetto javax.sound.sample non è adatto per l'elaborazione di file WAV se è necessario disporre dell'accesso ai valori di esempio effettivi. Il pacchetto ti consente di cambiare volume, frequenza di campionamento, ecc., Ma se vuoi altri effetti (ad esempio aggiungendo un'eco), sei da solo. (Il tutorial Java accenna che dovrebbe essere possibile trattare direttamente i valori del campione, ma lo scrittore tecnico promesse eccessive.)
Questo sito ha una semplice classe per i file di elaborazione WAV: http://www.labbookpages.co.uk/audio/javaWavFiles.html
+1 per il collegamento alla classe WavFile. In realtà non è molto più semplice di così. – Grodriguez
Ho letto i file WAV attraverso un AudioInputStream
. Il seguente frammento di Java Sound Tutorials funziona correttamente.
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...
}
Per scrivere un WAV, l'ho trovato abbastanza complicato. In superficie sembra un problema circolare, il comando che scrive si basa su un parametro AudioInputStream
.
Ma come si scrive un byte a AudioInputStream
? Non dovrebbe esserci un AudioOutputStream
?
Quello che ho trovato è che si può definire un oggetto che ha accesso ai dati del byte audio grezzo per implementare TargetDataLine
.
Ciò richiede l'implementazione di molti metodi, ma la maggior parte può rimanere in forma fittizia in quanto non sono necessari per la scrittura di dati in un file. Il metodo chiave da implementare è read(byte[] buffer, int bufferoffset, int numberofbytestoread)
.
Poiché questo metodo verrà probabilmente chiamato più volte, dovrebbe esserci anche una variabile di istanza che indichi la distanza tra i dati di cui si è passati e l'aggiornamento come parte del metodo read
.
Dopo aver implementato questo metodo, allora il vostro oggetto può essere utilizzato per creare un nuovo AudioInputStream
che a sua volta può essere utilizzato con:
AudioSystem.write(yourAudioInputStream, AudioFileFormat.WAV, yourFileDestination)
Come promemoria, un
AudioInputStream
possono essere creati con aTargetDataLine
come fonte.
Quanto alla diretta manipolare i dati, ho avuto buon successo agendo sui dati nel buffer nel ciclo più interno del frammento di esempio di cui sopra, audioBytes
.
Mentre si è in quel ciclo interno, è possibile convertire i byte a numeri interi o galleggianti e moltiplicare un valore volume
(che vanno da 0.0
a 1.0
) e poi convertire di nuovo a pochi byte endian.
Credo che, dal momento che si ha accesso a una serie di campioni in quel buffer, è possibile coinvolgere anche varie forme di algoritmi di filtraggio DSP in quella fase. Nella mia esperienza ho scoperto che è meglio fare cambiamenti di volume direttamente sui dati in questo buffer, perché quindi è possibile fare l'incremento più piccolo possibile: un delta per campione, riducendo al minimo la possibilità di scatti dovuti a discontinuità indotte dal volume.
Trovo che le "linee di controllo" per il volume fornito da Java tendono a situazioni in cui i salti nel volume causano clic, e credo che questo sia dovuto al fatto che i delta vengono implementati solo alla granularità di un singolo buffer letto (spesso in l'intervallo di una modifica per 1024 campioni) anziché dividere la modifica in parti più piccole e aggiungerne una per campione. Ma non sono al corrente di come sono stati implementati i controlli del volume, quindi per favore prendi quella congettura con un pizzico di sale.
Tutti e tutti, Java.Sound è stato un vero rompicapo da capire. Guasto il tutorial per non includere un esplicito esempio di scrittura di un file direttamente da byte. Guasto il tutorial per seppellire il miglior esempio di codifica Play a File nella sezione "Come convertire ...". Tuttavia, ci sono MOLTE preziose informazioni GRATUITE in questo tutorial.
EDIT: 12/13/17
allora ho usato il seguente codice per scrivere l'audio da un file PCM nei miei progetti. Invece di implementare TargetDataLine
, è possibile estendere InputStream
e utilizzarlo come parametro per il metodo 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);
}
}
L'origine dati da esportare qui è sotto forma di stereo galleggianti variano da -1 a 1. Il formato del flusso risultante è 16 bit, stereo, little endian.
Ho omesso i metodi skip
e markSupported
per la mia particolare applicazione. Ma non dovrebbe essere difficile aggiungerli se sono necessari.
WAV File Specification https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
C'è un'API per il vostro scopo http://code.google.com/p/musicg/
Io uso FileInputStream
con qualche 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();
}
vostri valori dei campioni sono in short[] input
!
cosa vuol dire: file.length() - 44 ... come sei arrivato a quei numeri –
quale sarebbe la dimensione di int [] ??? –
Questo è il codice sorgente per scrivere direttamente in un file wav. Hai solo bisogno di conoscere la matematica e l'ingegneria del suono per produrre il suono che desideri. In questo esempio l'equazione calcola un battito binaurale.
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();
}
}
Se si potesse modificare questo per creare un sub bassi hip hop che sarebbe bello perché è attualmente quello che sto cercando di modificare questo programma per fare.
Il mio problema principale era quello stesso voodoo. Volevo vedere se c'erano codice/classe pronti a farlo. Penso di esserci riuscito, usando AudioSystem e AudioInputStream. Il trucco era di invertire l'ordine dei byte in ciascun campione sonoro prima di convertirlo in breve, poiché WAV codifica i valori numerici in modo little-Endian. Grazie, Yonatan. – yonatan