Java - WAV ファイルの読み取り、操作、書き込み 質問する

Java - WAV ファイルの読み取り、操作、書き込み 質問する

Javaプログラムでオーディオファイルを読み取る最適な方法は何ですか(WAVAファイルファイル) を数値の配列 ( float[]、、short[]...) に変換し、数値の配列から WAV ファイルを書き込むことはできますか?

ベストアンサー1

私はWAVファイルを 経由で読みますAudioInputStream。次のスニペットはJava サウンド チュートリアルうまく動作します。

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 フィルタリング アルゴリズムも使用できると思います。私の経験では、このバッファ内のデータに対して直接ボリューム変更を行う方がよいことがわかりました。そうすることで、サンプルごとに 1 つのデルタという最小限の増分で済み、ボリュームによる不連続性によるクリックの可能性を最小限に抑えることができるからです。

Java が提供するボリュームの「コントロール ライン」は、ボリュームの急激な変化によってクリック音が発生する状況になりやすいようです。これは、デルタが、変更を小さな部分に分割してサンプルごとに 1 つずつ追加するのではなく、単一のバッファー読み取りの粒度 (多くの場合、1024 サンプルあたり 1 つの変更の範囲) でのみ実装されているためだと考えています。ただし、ボリューム コントロールがどのように実装されたかについては詳しくありませんので、この推測は鵜呑みにしないでください。

全体的に見て、Java.Sound を理解するのは本当に頭の痛い作業でした。チュートリアルに、バイトから直接ファイルを書き込む明確な例が含まれていないのが問題です。チュートリアルで、Play a File コーディングの最良の例が「変換方法...」セクションに埋もれているのが問題です。しかし、そのチュートリアルには、貴重な無料情報が大量に含まれています。


編集: 2017年12月13日

それ以来、私は自分のプロジェクトで PCM ファイルからオーディオを書き込むために次のコードを使用しています。実装する代わりに、それをTargetDataLine拡張してメソッドInputStreamのパラメータとして使用することができますAudioSystem.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 ビット、ステレオ、リトルエンディアンです。

私は特定のアプリケーションのためにメソッドを省略しましたskipmarkSupportedしかし、必要であれば追加することは難しくないはずです。

おすすめ記事