2016-09-02 3 views
5

연구와 많은 시행 착오 끝에 나는이라는 요소가 인 것으로 생각되는 스펙트로 그램을 만들 수 있다는 사실을 알게되었습니다.자바에서 FFT를 사용하여 .wav에서 스펙트로 그램 만들기

먼저 .wav 파일을 바이트 배열로 읽어 데이터 부분 만 추출합니다.

2. 바이트 배열을 오른쪽 및 왼쪽 채널의 평균을 취하는 double 배열로 변환합니다. 또한 1 채널의 1 샘플은 2 바이트로 구성됩니다. 그래서, 4 바이트는 1 더블.

특정 윈도우 크기가 2 인 경우 here에서 FFT를 적용하고 주파수 도메인에서 진폭을 구합니다. 이것은 스펙트로 그램 이미지의 수직 스트립입니다.

4. 동일한 창 크기로 반복적으로 수행하고 전체 데이터에 대해 겹쳐서 스펙트로 그램을 얻습니다.

다음

import java.io.IOException; 
import java.nio.ByteBuffer; 
import java.nio.file.Files; 
import java.nio.file.Path; 
import java.nio.file.Paths; 
import java.util.Arrays; 

public class readWAV2Array { 

    private byte[] entireFileData; 

    //SR = sampling rate 
    public double getSR(){ 
     ByteBuffer wrapped = ByteBuffer.wrap(Arrays.copyOfRange(entireFileData, 24, 28)); // big-endian by default 
     double SR = wrapped.order(java.nio.ByteOrder.LITTLE_ENDIAN).getInt(); 
     return SR; 
    } 

    public readWAV2Array(String filepath, boolean print_info) throws IOException{ 
     Path path = Paths.get(filepath); 
     this.entireFileData = Files.readAllBytes(path); 

     if (print_info){ 

     //extract format 
     String format = new String(Arrays.copyOfRange(entireFileData, 8, 12), "UTF-8"); 

     //extract number of channels 
     int noOfChannels = entireFileData[22]; 
     String noOfChannels_str; 
     if (noOfChannels == 2) 
      noOfChannels_str = "2 (stereo)"; 
     else if (noOfChannels == 1) 
      noOfChannels_str = "1 (mono)"; 
     else 
      noOfChannels_str = noOfChannels + "(more than 2 channels)"; 

     //extract sampling rate (SR) 
     int SR = (int) this.getSR(); 

     //extract Bit Per Second (BPS/Bit depth) 
     int BPS = entireFileData[34]; 

     System.out.println("---------------------------------------------------"); 
     System.out.println("File path:   " + filepath); 
     System.out.println("File format:  " + format); 
     System.out.println("Number of channels: " + noOfChannels_str); 
     System.out.println("Sampling rate:  " + SR); 
     System.out.println("Bit depth:   " + BPS); 
     System.out.println("---------------------------------------------------"); 

     } 
    } 

    public double[] getByteArray(){ 
     byte[] data_raw = Arrays.copyOfRange(entireFileData, 44, entireFileData.length); 
     int totalLength = data_raw.length; 

     //declare double array for mono 
     int new_length = totalLength/4; 
     double[] data_mono = new double[new_length]; 

     double left, right; 
     for (int i = 0; i < new_length; i++){ 
      left = ((data_raw[i] & 0xff) << 8) | (data_raw[i+1] & 0xff); 
      right = ((data_raw[i+2] & 0xff) << 8) | (data_raw[i+3] & 0xff); 
      data_mono[i] = (left+right)/2.0; 
     }  
     return data_mono; 
    } 
} 

다음 코드는 메인 프로그램이

import java.awt.Color; 
import java.awt.image.BufferedImage; 
import java.io.File; 
import java.io.IOException; 
import java.util.Arrays; 

import javax.imageio.ImageIO; 

public class App { 

    public static Color getColor(double power) { 
     double H = power * 0.4; // Hue (note 0.4 = Green, see huge chart below) 
     double S = 1.0; // Saturation 
     double B = 1.0; // Brightness 

     return Color.getHSBColor((float)H, (float)S, (float)B); 
    } 

    public static void main(String[] args) { 
     // TODO Auto-generated method stub 
     String filepath = "audio_work/Sine_Sweep_Full_Spectrum_20_Hz_20_kHz_audiocheck.wav"; 
     try { 

      //get raw double array containing .WAV data 
      readWAV2Array audioTest = new readWAV2Array(filepath, true); 
      double[] rawData = audioTest.getByteArray(); 
      int length = rawData.length; 

      //initialize parameters for FFT 
      int WS = 2048; //WS = window size 
      int OF = 8; //OF = overlap factor 
      int windowStep = WS/OF; 

      //calculate FFT parameters 
      double SR = audioTest.getSR(); 
      double time_resolution = WS/SR; 
      double frequency_resolution = SR/WS; 
      double highest_detectable_frequency = SR/2.0; 
      double lowest_detectable_frequency = 5.0*SR/WS; 

      System.out.println("time_resolution:    " + time_resolution*1000 + " ms"); 
      System.out.println("frequency_resolution:   " + frequency_resolution + " Hz"); 
      System.out.println("highest_detectable_frequency: " + highest_detectable_frequency + " Hz"); 
      System.out.println("lowest_detectable_frequency: " + lowest_detectable_frequency + " Hz"); 

      //initialize plotData array 
      int nX = (length-WS)/windowStep; 
      int nY = WS; 
      double[][] plotData = new double[nX][nY]; 

      //apply FFT and find MAX and MIN amplitudes 

      double maxAmp = Double.MIN_VALUE; 
      double minAmp = Double.MAX_VALUE; 

      double amp_square; 

      double[] inputImag = new double[length]; 

      for (int i = 0; i < nX; i++){ 
       Arrays.fill(inputImag, 0.0); 
       double[] WS_array = FFT.fft(Arrays.copyOfRange(rawData, i*windowStep, i*windowStep+WS), inputImag, true); 
       for (int j = 0; j < nY; j++){ 
        amp_square = (WS_array[2*j]*WS_array[2*j]) + (WS_array[2*j+1]*WS_array[2*j+1]); 
        if (amp_square == 0.0){ 
         plotData[i][j] = amp_square; 
        } 
        else{ 
         plotData[i][j] = 10 * Math.log10(amp_square); 
        } 

        //find MAX and MIN amplitude 
        if (plotData[i][j] > maxAmp) 
         maxAmp = plotData[i][j]; 
        else if (plotData[i][j] < minAmp) 
         minAmp = plotData[i][j]; 

       } 
      } 

      System.out.println("---------------------------------------------------"); 
      System.out.println("Maximum amplitude: " + maxAmp); 
      System.out.println("Minimum amplitude: " + minAmp); 
      System.out.println("---------------------------------------------------"); 

      //Normalization 
      double diff = maxAmp - minAmp; 
      for (int i = 0; i < nX; i++){ 
       for (int j = 0; j < nY; j++){ 
        plotData[i][j] = (plotData[i][j]-minAmp)/diff; 
       } 
      } 

      //plot image 
      BufferedImage theImage = new BufferedImage(nX, nY, BufferedImage.TYPE_INT_RGB); 
      double ratio; 
      for(int x = 0; x<nX; x++){ 
       for(int y = 0; y<nY; y++){ 
        ratio = plotData[x][y]; 

        //theImage.setRGB(x, y, new Color(red, green, 0).getRGB()); 
        Color newColor = getColor(1.0-ratio); 
        theImage.setRGB(x, y, newColor.getRGB()); 
       } 
      } 
      File outputfile = new File("saved.png"); 
      ImageIO.write(theImage, "png", outputfile); 

     } catch (IOException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
    } 

} 

그러나, 이미지 내가 얻을 실행하는 것입니다 이중 배열로 읽기 .WAV 코드입니다 .wav에서 20-20kHz의 전반적인 소리 재생은 다음과 같습니다.

enter image description here : -

색 소리 적색 (높음)의 강도 보여> 녹색 (낮음) 우측으로

enter image description here

을, 그 아래의 그림과 같이 보일 것이다

내 프로젝트에서 어떤 수정/개선/제안을받을 수 있다면 정말 고마워 할 것입니다. 제 질문에 대해 미리 감사드립니다.

+0

프로그램의 스펙트로 그램 이미지를 설명 할 수 있습니까? 어떤 축이 시간이며 어떤 빈도입니까? 색상을 해석하기가 어렵습니다. 그레이 스케일을 사용할 수 있습니까? –

+0

x 축은 시간이고 y 축은 빈도입니다. 빨간색은 높은 진폭, 녹색은 낮은 진폭입니다. – Aung

답변

6

다행히도 당신이 잘못한 것보다 더 많은 권리가있는 것 같습니다.

여분의 빨간 선이 나타나는 첫 번째 주요 문제는 readWAV2Array.getByteArray의 데이터를 어떻게 디코딩 했느냐 때문입니다. 샘플은 4 바이트를 차지하므로 4의 배수로 인덱싱해야합니다 (예 : 샘플 0의 경우 바이트 0,1,2,3, 샘플 1의 경우 바이트 4,5,6,7). 그렇지 않으면 4 바이트의 겹치는 블록을 읽습니다. (예 : 0 번 바이트의 경우 0,1,2,3 바이트, 1 번 바이트의 경우 1,2,3,4 바이트). 이 변환의 또 다른 사항은 부호가있는 16 비트 결과를 부호없는 바이트에서 가져 오기 위해 leftright (유형은 double)으로 할당하기 전에 부호가있는 short 유형으로 결과를 명시 적으로 캐스팅해야한다는 것입니다.

fixed decoding

그러나 당신 : 당신이 당신의 20Hz에서-20kHz의 짹짹을 나타내는 강한 선이 플롯을 얻기 위해 시작해야이 시점에서

for (int i = 0; 4*i+3 < totalLength; i++){ 
    left = (short)((data_raw[4*i+1] & 0xff) << 8) | (data_raw[4*i] & 0xff); 
    right = (short)((data_raw[4*i+3] & 0xff) << 8) | (data_raw[4*i+2] & 0xff); 
    data_mono[i] = (left+right)/2.0; 
}  

:처럼 이것은 당신에게 보이는 변환 루프를 제공한다 실제로 2 줄을 알아 차려야합니다. 이것은 실수 값 신호의 경우 주파수 스펙트럼이 허미 시안 대칭을 가지기 때문입니다. 나이 퀴 스트 주파수 (샘플링 속도의 절반,이 경우 44100Hz/2) 위의 스펙트럼의 크기는 따라서 나이 퀴 스트 주파수 아래의 스펙트럼을 중복 반영합니다. 만 나이 퀴 스트 주파수 아래의 비 중복 부분을 플롯에 mainnY의 정의를 변경함으로써 달성 될 수있다 :

int nY = WS/2 + 1; 

당신에게 줄 것이다 :

only non-redundant spectrum

을 우리가있어 거의 무엇 찾고 있지만 빈도가 증가하는 스윕은 감소하는 선이있는 그림을 생성합니다. 이는 그림의 맨 위에있는 인덱스 0에서 0Hz 주파수를 만들고 그림의 맨 아래에있는 인덱스 인 nY-1에서 22050Hz 주파수를 생성하기 때문입니다. 주변에 그림을 뒤집어 상단에서 하단과 22050Hz의 평소 0Hz에서 활용하려면 사용하는 색인을 변경할 수 있습니다

plotData[i][nY-j-1] = 10 * Math.log10(amp_square); 

지금 당신은 당신이 기대했던 것 같이 보이는 플롯이 있어야합니다 () 다른 색상의지도 비록 :

0Hz at the bottom

마지막 주 : 나는 데시벨로 전환 0의 로그를 복용이 특정의 선형 규모의 크기에 출력을 설정 피하기 위해 당신의 의도를 이해하면서 예기치 않은 결과가 발생할 수 있습니다. 대신 보호를 위해 차단 임계 값 진폭을 선택합니다.

// select threshold based on the expected spectrum amplitudes 
// e.g. 80dB below your signal's spectrum peak amplitude 
double threshold = 1.0; 
// limit values and convert to dB 
plotData[i][nY-j-1] = 10 * Math.log10(Math.max(amp_square,threshold)); 
+0

내 코드를보고 포괄적이고 자세한 설명을하는 데 시간을 보내 주셔서 감사합니다. 나는 교정을 적용했지만, 나는 가지고있는 모양이 너와 같지 않다고 생각한다. 여분의 선이 제거되었지만 위 아래로 직선이 있습니다. 나는 당신이 코멘트에 넣지 않은 한 가지 더 교정이있을 것이라고 생각합니다. 그렇다면 조언하십시오. 다시 한번 고마워. – Aung

+0

이것은 모든 변경 사항을 적용한 후 얻은 이미지입니다. [이미지를 보려면 여기를 클릭하십시오.] (https://s21.postimg.org/j5el9qzjr/saved.png). – Aung

+0

나는 그것에 대한 해답이있을 것 같아. 내 .wav 20 - 20kHz는 스테레오가 아닌 모노입니다. 나는 그것을 고치고 그 결과를 게시하려고 노력할 것이다. 고맙습니다. – Aung