Java-lecture, manipulation et écriture de fichiers WAV

dans un programme Java, Quelle est la meilleure façon de lire un fichier audio ( WAV fichier) à un tableau de nombres ( float[] , short[] , ...), et d'écrire un fichier WAV à partir d'un tableau de nombres?

15
demandé sur jwueller 2010-07-21 13:14:41

8 réponses

plus de détails sur ce que vous souhaitez réaliser serait utile. Si les données wav brutes sont acceptables pour vous, utilisez simplement un FileInputStream et probablement un Scanner pour les transformer en nombres. Mais permettez-moi de vous donner un exemple de code significatif pour vous aider à commencer:

il y a une classe appelée com.soleil.Média.son.WaveFileWriter à cet effet.

InputStream in = ...;
OutputStream out = ...;

AudioInputStream in = AudioSystem.getAudioInputStream(in);

WaveFileWriter writer = new WaveFileWriter();
writer.write(in, AudioFileFormat.Type.WAVE, outStream);

vous pourriez mettre en œuvre votre propre AudioInputStream qui fait tout ce voodoo pour tourner votre tableaux de nombres en données audio.

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

comme @stacker mentionné, vous devriez vous familiariser avec L'API bien sûr.

7
répondu sfussenegger 2017-05-23 12:17:08

j'ai lu des fichiers WAV via un AudioInputStream . L'extrait suivant des tutoriels Java Sound fonctionne 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...
}

pour écrire une WAV, j'ai trouvé ça assez délicat. En surface, cela ressemble à un problème circulaire, la commande qui écrit s'appuie sur un paramètre AudioInputStream .

mais comment écrire des octets à un AudioInputStream ? Ne devrait-il pas y avoir un AudioOutputStream ?

Ce Que Je trouvé était que l'on peut définir un objet qui a accès aux données brutes byte audio pour mettre en œuvre TargetDataLine .

cela nécessite un grand nombre de méthodes doivent être mises en œuvre, mais la plupart peuvent rester dans la forme factice car ils ne sont pas nécessaires pour écrire des données à un dossier. La principale méthode de mise en œuvre est read(byte[] buffer, int bufferoffset, int numberofbytestoread) .

comme cette méthode sera probablement appelée plusieurs fois, il devrait y avoir aussi une variable d'instance qui indique à quelle distance à travers les données on a a progressé, et mettre à jour cela dans le cadre de la méthode read ci-dessus.

lorsque vous avez implémenté cette méthode, alors votre objet peut être utilisé pour créer un nouveau AudioInputStream qui à son tour peut être utilisé avec:

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

pour rappel, un AudioInputStream peut être créé avec un TargetDataLine comme source.

Comme à la manipulation des données, j'ai eu un bon succès en agissant sur le les données dans le tampon dans la boucle la plus interne de l'exemple de snippet ci-dessus, audioBytes .

pendant que vous êtes dans cette boucle interne, vous pouvez convertir les octets en entiers ou en flotteurs et multiplier une valeur volume (allant de 0.0 à 1.0 ) et ensuite les convertir de nouveau en petits octets endiens.

je crois que puisque vous avez accès à une série d'échantillons dans ce tampon, vous pouvez également engager diverses formes d'algorithmes de filtrage DSP à cette étape. Dans mon expérience j'ai trouvé qu'il est préférable de faire des changements de volume directement sur les données dans ce tampon parce qu'alors vous pouvez faire le plus petit incrément possible: un delta par échantillon, en minimisant la chance de clics dus à des discontinuités induites par le volume.

je trouve que les" lignes de contrôle " pour le volume fourni par Java tendent à des situations où les sauts de volume causeront des clics, et je crois que c'est parce que les deltas ne sont mis en œuvre qu'à la granularité d'un seul tampon Lire (souvent dans l'intervalle d'un changement par 1024 échantillons) plutôt que de diviser le changement en plus petits morceaux et de les ajouter un par échantillon. Mais je ne suis pas au courant de la façon dont les contrôles de Volume ont été mis en œuvre, alors s'il vous plaît prenez cette conjecture avec un grain de sel.

tout et tout, Java.Le son a été un vrai casse-tête pour comprendre. J'accuse le tutoriel de ne pas inclure un exemple explicite d'écriture d'un fichier directement à partir d'octets. Je blâme le tutoriel pour enterrer le meilleur exemple de jouer un fichier codant dans le "comment convertir..." section. Cependant, il y a beaucoup d'informations gratuites dans ce tutoriel.


EDIT: 12/13/17

j'ai depuis utilisé le code suivant pour écrire l'audio d'un fichier PCM dans mes propres projets. Au lieu de mettre en œuvre TargetDataLine , on peut étendre InputStream et l'utiliser comme paramètre à la méthode 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);
    }
}

les données sources à exporter ici sont des flotteurs stéréophoniques allant de -1 à 1. Le format du flux résultant est de 16 bits, stéréo, little-endian.

j'ai omis les méthodes skip et markSupported pour mon application particulière. Mais il ne devrait pas être difficile de les ajouter si elles sont nécessaires.

6
répondu Phil Freihofner 2017-12-13 18:24:24

le javax.son.sample package n'est pas adapté pour le traitement des fichiers WAV si vous avez besoin d'avoir accès aux valeurs d'échantillon réelles. Le paquet vous permet de changer le volume, le taux d'échantillonnage, etc., mais si vous voulez d'autres effets (par exemple, ajouter un écho), vous êtes sur votre propre. (Le tutoriel Java suggère qu'il devrait être possible de traiter les valeurs de l'échantillon directement, mais le rédacteur technique a fait trop de compromis.)

ce site a une classe simple pour le traitement des fichiers WAV: http://www.labbookpages.co.uk/audio/javaWavFiles.html

5
répondu cayhorstmann 2011-03-20 00:54:44

Spécification de Fichier WAV https://ccrma.stanford.edu/courses/422/projects/WaveFormat/

il y a une API pour votre utilisation http://code.google.com/p/musicg/

5
répondu c'quet 2012-05-20 03:05:26

C'est le code source pour écrire directement dans un fichier wav. Vous avez juste besoin de connaître les mathématiques et l'ingénierie du son pour produire le son que vous voulez. Dans cet exemple, l'équation calcule un battement 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 vous pouviez modifier ceci pour créer une basse hip hop sub qui serait cool parce que c'est ce que j'essaie actuellement de modifier ce programme pour le faire.

4
répondu Joshua Beckford 2015-03-09 02:55:49

tout d'abord, vous pouvez avoir besoin de connaître les en-têtes et les positions de données d'une structure D'onde, vous pouvez trouver le spec ici . Soyez conscient que les données sont peu endian.

il y a un API qui peut vous aider à atteindre votre objectif.

2
répondu jonsca 2012-05-20 06:33:24
Les fichiers Wave

sont supportés par le javax .son.exemple de colis

comme ce N'est pas une API triviale, vous devriez lire un article / tutoriel qui introduit l'API comme

Java Sound, An Introduction

1
répondu stacker 2013-11-02 18:37:39

j'utilise FileInputStream avec un peu de magie:

    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();
    }

vos valeurs d'échantillon sont dans short[] input !

-1
répondu user3316122 2014-11-22 14:47:46