Java - чтение, управление и запись файлов WAV - PullRequest
19 голосов
/ 21 июля 2010

В программе на Java, как лучше всего прочитать аудиофайл ( WAV файл) в массив чисел (float[], short[], ...) и записать файл WAV из массива чисел?

Ответы [ 8 ]

10 голосов
/ 19 июня 2011

Я читаю файлы WAV через AudioInputStream.Следующий фрагмент из Java Sound Tutorials работает хорошо.

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...
}

Чтобы написать WAV, я обнаружил, что это довольно сложно.На первый взгляд это кажется круговой проблемой: команда, которая записывает, использует AudioInputStream в качестве параметра.

Но как записать байты в AudioInputStream?Разве не должно быть AudioOutputStream?

Я обнаружил, что можно определить объект, который имеет доступ к необработанным аудиобайтовым данным для реализации TargetDataLine.

Для этого требуетсяМногие методы могут быть реализованы, но большинство из них могут оставаться в фиктивной форме, поскольку они не требуются для записи данных в файл.Ключевой метод для реализации - read(byte[] buffer, int bufferoffset, int numberofbytestoread).

Поскольку этот метод, вероятно, будет вызываться несколько раз, должна также существовать переменная экземпляра, которая указывает, как далеко продвигаются данные, и обновлять ее как часть вышеуказанного метода read.

Когда вы реализовали этот метод, ваш объект можно использовать для создания нового AudioInputStream, который, в свою очередь, можно использовать с:

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

В качестве напоминания, AudioInputStream может быть создан с TargetDataLine в качестве источника.

Что касается прямого манипулирования данными, я добился большого успеха, воздействуя на данные в буфере в самом внутреннем цикле фрагментапример выше, audioBytes.

Пока вы находитесь в этом внутреннем цикле, вы можете преобразовать байты в целые или с плавающей точкой и умножить значение volume (в диапазоне от 0.0 до 1.0), а затемпреобразовать их обратно в байты с прямым порядком байтов.

Я полагаю, поскольку у вас есть доступ к серии семплов в этом буфере, вы также можете задействовать различные формы алгоритмов фильтрации DSP на этом этапе.По своему опыту я обнаружил, что изменения объема лучше выполнять непосредственно в данных в этом буфере, потому что тогда вы можете сделать наименьшее возможное приращение: одна дельта на выборку, сводя к минимуму вероятность щелчков из-за разрывов, вызванных объемом.

Я нахожу, что «контрольные линии» для тома, предоставляемые Java, склонны к ситуациям, когда скачки громкости вызывают щелчки, и я считаю, что это связано с тем, что дельты реализуются только при гранулярности чтения из одного буфера (часто в пределах одного изменения на 1024 образца), а не делят изменение на более мелкие части и добавляют их по одному на образец.Но я не знаком с тем, как были реализованы средства управления громкостью, поэтому, пожалуйста, примите эту гипотезу с недоверием.

В общем, Java.Sound был настоящей головной болью, чтобы понять.Я виноват в том, что Учебное пособие не содержит явного примера записи файла непосредственно из байтов.Я виноват в том, что Учебник хоронит лучший пример кодирования Play a File в разделе «Как конвертировать ...».Тем не менее, в этом уроке очень много БЕСПЛАТНОЙ информации.


РЕДАКТИРОВАТЬ: 12/13/17

С тех пор я использовал следующий код для записи аудио из файла PCM в моих собственных проектах.Вместо реализации TargetDataLine можно расширить InputStream и использовать это в качестве параметра для метода 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);
    }
}

Исходные данные, которые будут экспортированы здесь, представлены в виде поплавков стерео, варьирующихся отОт -1 до 1. Формат результирующего потока - 16-битный, стереофонический, с прямым порядком байтов.

Я опустил методы skip и markSupported для моего конкретного приложения.Но добавить их, если они нужны, не составит труда.

8 голосов
/ 21 июля 2010

Было бы полезно узнать больше подробностей о том, чего бы вы хотели достичь. Если вам подходят необработанные данные WAV, просто используйте FileInputStream и, возможно, сканер, чтобы превратить их в числа. Но позвольте мне дать вам несколько примеров кода, чтобы вы могли начать:

Для этой цели существует класс com.sun.media.sound.WaveFileWriter.

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

AudioInputStream in = AudioSystem.getAudioInputStream(in);

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

Вы можете реализовать свой собственный AudioInputStream, который делает все, что угодно, чтобы превратить ваши числовые массивы в аудиоданные.

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

Как уже упоминалось @ stacker , вы, конечно, должны ознакомиться с API.

6 голосов
/ 20 марта 2011

Пакет javax.sound.sample не подходит для обработки файлов WAV, если вам нужен доступ к фактическим значениям выборки. Пакет позволяет вам изменять громкость, частоту дискретизации и т. Д., Но если вам нужны другие эффекты (например, добавление эха), вы сами по себе. (Учебник по Java намекает на то, что должна быть возможность обрабатывать значения образцов напрямую, но технический писатель слишком обнадежил.)

На этом сайте есть простой класс для обработки файлов WAV: http://www.labbookpages.co.uk/audio/javaWavFiles.html

5 голосов
/ 10 ноября 2011

Спецификация файла WAV https://ccrma.stanford.edu/courses/422/projects/WaveFormat/

Существует API для вашей цели http://code.google.com/p/musicg/

4 голосов
/ 09 марта 2015

Это исходный код для записи непосредственно в файл WAV.Вам просто нужно знать математику и звуковую инженерию, чтобы получить звук, который вы хотите.В этом примере уравнение вычисляет бинауральный ритм.

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

}

Если бы вы могли изменить это, чтобы создать басовый хип-хоп, который был бы крут, потому что в настоящее время я пытаюсь изменить эту программу.

2 голосов
/ 10 ноября 2011

Прежде всего вам может понадобиться знать заголовки и позиции данных структуры WAVE, здесь вы можете найти спецификацию .Имейте в виду, что данные имеют порядок байтов.

Существует API , который может помочь вам достичь вашей цели.

2 голосов
/ 21 июля 2010

Волновые файлы поддерживаются пакетом javax.sound.sample

Так как API не является тривиальным, вы должны прочитать статью / руководство, которое представляет API, например

Звук Явы, Введение

0 голосов
/ 22 ноября 2014

Я использую FileInputStream с некоторой магией:

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

Ваш пример значений в short[] input!

...