2014-04-24 6 views
2

주어진 파일의 오디오 파형을 그리는 코드 스 니펫을 상속했습니다. 그러나이 파형은 레이블링, 축 정보 등을 사용하지 않고 JAVA 벡터 그래픽을 사용하여 작성된 간단한 이미지입니다. jfreechart로 이식하여 유익한 가치를 높이고 싶습니다. 내 문제는 그 코드가 적어도 말하기에는 비밀 스럽다는 것이다.jfreechart (Amplitude vs time)을 사용하여 오디오 신호 플로팅

public class Plotter { 
AudioInputStream audioInputStream; 
Vector<Line2D.Double> lines = new Vector<Line2D.Double>(); 
String errStr; 
Capture capture = new Capture(); 
double duration, seconds; 
//File file; 
String fileName = "out.png"; 
SamplingGraph samplingGraph; 
String waveformFilename; 
Color imageBackgroundColor = new Color(20,20,20); 

public Plotter(URL url, String waveformFilename) throws Exception { 
    if (url != null) { 
     try { 
      errStr = null; 
      this.fileName = waveformFilename; 
      audioInputStream = AudioSystem.getAudioInputStream(url); 
      long milliseconds = (long)((audioInputStream.getFrameLength() * 1000)/audioInputStream.getFormat().getFrameRate()); 
      duration = milliseconds/1000.0; 
      samplingGraph = new SamplingGraph(); 
      samplingGraph.createWaveForm(null);  

     } catch (Exception ex) { 
      reportStatus(ex.toString()); 
      throw ex; 
     } 
    } else { 
     reportStatus("Audio file required."); 
    } 
} 
/** 
* Render a WaveForm. 
*/ 
class SamplingGraph implements Runnable { 

    private Thread thread; 
    private Font font10 = new Font("serif", Font.PLAIN, 10); 
    private Font font12 = new Font("serif", Font.PLAIN, 12); 
    Color jfcBlue = new Color(000, 000, 255); 
    Color pink = new Color(255, 175, 175); 


    public SamplingGraph() { 
    } 


    public void createWaveForm(byte[] audioBytes) { 

     lines.removeAllElements(); // clear the old vector 

     AudioFormat format = audioInputStream.getFormat(); 
     if (audioBytes == null) { 
      try { 
       audioBytes = new byte[ 
        (int) (audioInputStream.getFrameLength() 
        * format.getFrameSize())]; 
       audioInputStream.read(audioBytes); 
      } catch (Exception ex) { 
       reportStatus(ex.getMessage()); 
       return; 
      } 
     } 
     int w = 500; 
     int h = 200; 
     int[] audioData = null; 
     if (format.getSampleSizeInBits() == 16) { 
      int nlengthInSamples = audioBytes.length/2; 
      audioData = new int[nlengthInSamples]; 
      if (format.isBigEndian()) { 
       for (int i = 0; i < nlengthInSamples; i++) { 
        /* First byte is MSB (high order) */ 
        int MSB = (int) audioBytes[2*i]; 
        /* Second byte is LSB (low order) */ 
        int LSB = (int) audioBytes[2*i+1]; 
        audioData[i] = MSB << 8 | (255 & LSB); 
       } 
      } else { 
       for (int i = 0; i < nlengthInSamples; i++) { 
        /* First byte is LSB (low order) */ 
        int LSB = (int) audioBytes[2*i]; 
        /* Second byte is MSB (high order) */ 
        int MSB = (int) audioBytes[2*i+1]; 
        audioData[i] = MSB << 8 | (255 & LSB); 
       } 
      } 
     } else if (format.getSampleSizeInBits() == 8) { 
      int nlengthInSamples = audioBytes.length; 
      audioData = new int[nlengthInSamples]; 
      if (format.getEncoding().toString().startsWith("PCM_SIGN")) { 
       for (int i = 0; i < audioBytes.length; i++) { 
        audioData[i] = audioBytes[i]; 
       } 
      } else { 
       for (int i = 0; i < audioBytes.length; i++) { 
        audioData[i] = audioBytes[i] - 128; 
       } 
      } 
     } 

     int frames_per_pixel = audioBytes.length/format.getFrameSize()/w; 
     byte my_byte = 0; 
     double y_last = 0; 
     int numChannels = format.getChannels(); 
     for (double x = 0; x < w && audioData != null; x++) { 
      int idx = (int) (frames_per_pixel * numChannels * x); 
      if (format.getSampleSizeInBits() == 8) { 
       my_byte = (byte) audioData[idx]; 
      } else { 
       my_byte = (byte) (128 * audioData[idx]/32768); 
      } 
      double y_new = (double) (h * (128 - my_byte)/256); 
      lines.add(new Line2D.Double(x, y_last, x, y_new)); 
      y_last = y_new; 
     } 
     saveToFile(); 
    } 


    public void saveToFile() {    
     int w = 500; 
     int h = 200; 
     int INFOPAD = 15; 

     BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); 
     Graphics2D g2 = bufferedImage.createGraphics(); 

     createSampleOnGraphicsContext(w, h, INFOPAD, g2);    
     g2.dispose(); 
     // Write generated image to a file 
     try { 
      // Save as PNG 
      File file = new File(fileName); 
      System.out.println(file.getAbsolutePath()); 
      ImageIO.write(bufferedImage, "png", file); 
      JOptionPane.showMessageDialog(null, 
        new JLabel(new ImageIcon(fileName))); 
     } catch (IOException e) { 
     } 
    } 


    private void createSampleOnGraphicsContext(int w, int h, int INFOPAD, Graphics2D g2) {    
     g2.setBackground(imageBackgroundColor); 
     g2.clearRect(0, 0, w, h); 
     g2.setColor(Color.white); 
     g2.fillRect(0, h-INFOPAD, w, INFOPAD); 

     if (errStr != null) { 
      g2.setColor(jfcBlue); 
      g2.setFont(new Font("serif", Font.BOLD, 18)); 
      g2.drawString("ERROR", 5, 20); 
      AttributedString as = new AttributedString(errStr); 
      as.addAttribute(TextAttribute.FONT, font12, 0, errStr.length()); 
      AttributedCharacterIterator aci = as.getIterator(); 
      FontRenderContext frc = g2.getFontRenderContext(); 
      LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc); 
      float x = 5, y = 25; 
      lbm.setPosition(0); 
      while (lbm.getPosition() < errStr.length()) { 
       TextLayout tl = lbm.nextLayout(w-x-5); 
       if (!tl.isLeftToRight()) { 
        x = w - tl.getAdvance(); 
       } 
       tl.draw(g2, x, y += tl.getAscent()); 
       y += tl.getDescent() + tl.getLeading(); 
      } 
     } else if (capture.thread != null) { 
      g2.setColor(Color.black); 
      g2.setFont(font12); 
      //g2.drawString("Length: " + String.valueOf(seconds), 3, h-4); 
     } else { 
      g2.setColor(Color.black); 
      g2.setFont(font12); 
      //g2.drawString("File: " + fileName + " Length: " + String.valueOf(duration) + " Position: " + String.valueOf(seconds), 3, h-4); 

      if (audioInputStream != null) { 
       // .. render sampling graph .. 
       g2.setColor(jfcBlue); 
       for (int i = 1; i < lines.size(); i++) { 
        g2.draw((Line2D) lines.get(i)); 
       } 

       // .. draw current position .. 
       if (seconds != 0) { 
        double loc = seconds/duration*w; 
        g2.setColor(pink); 
        g2.setStroke(new BasicStroke(3)); 
        g2.draw(new Line2D.Double(loc, 0, loc, h-INFOPAD-2)); 
       } 
      } 
     } 
    } 

    public void start() { 
     thread = new Thread(this); 
     thread.setName("SamplingGraph"); 
     thread.start(); 
     seconds = 0; 
    } 

    public void stop() { 
     if (thread != null) { 
      thread.interrupt(); 
     } 
     thread = null; 
    } 

    public void run() { 
     seconds = 0; 
     while (thread != null) { 
      if ((capture.line != null) && (capture.line.isActive())) { 
       long milliseconds = (long)(capture.line.getMicrosecondPosition()/1000); 
       seconds = milliseconds/1000.0; 
      } 
      try { thread.sleep(100); } catch (Exception e) { break; }        
      while ((capture.line != null && !capture.line.isActive())) 
      { 
       try { thread.sleep(10); } catch (Exception e) { break; } 
      } 
     } 
     seconds = 0; 
    } 
} // End class SamplingGraph 

/** 
* Reads data from the input channel and writes to the output stream 
*/ 
class Capture implements Runnable { 

    TargetDataLine line; 
    Thread thread; 

    public void start() { 
     errStr = null; 
     thread = new Thread(this); 
     thread.setName("Capture"); 
     thread.start(); 
    } 

    public void stop() { 
     thread = null; 
    } 

    private void shutDown(String message) { 
     if ((errStr = message) != null && thread != null) { 
      thread = null; 
      samplingGraph.stop();     
      System.err.println(errStr); 
     } 
    } 

    public void run() { 

     duration = 0; 
     audioInputStream = null; 

     // define the required attributes for our line, 
     // and make sure a compatible line is supported. 

     AudioFormat format = audioInputStream.getFormat(); 
     DataLine.Info info = new DataLine.Info(TargetDataLine.class, 
      format); 

     if (!AudioSystem.isLineSupported(info)) { 
      shutDown("Line matching " + info + " not supported."); 
      return; 
     } 

     // get and open the target data line for capture. 

     try { 
      line = (TargetDataLine) AudioSystem.getLine(info); 
      line.open(format, line.getBufferSize()); 
     } catch (LineUnavailableException ex) { 
      shutDown("Unable to open the line: " + ex); 
      return; 
     } catch (SecurityException ex) { 
      shutDown(ex.toString()); 
      //JavaSound.showInfoDialog(); 
      return; 
     } catch (Exception ex) { 
      shutDown(ex.toString()); 
      return; 
     } 

     // play back the captured audio data 
     ByteArrayOutputStream out = new ByteArrayOutputStream(); 
     int frameSizeInBytes = format.getFrameSize(); 
     int bufferLengthInFrames = line.getBufferSize()/8; 
     int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes; 
     byte[] data = new byte[bufferLengthInBytes]; 
     int numBytesRead; 

     line.start(); 

     while (thread != null) { 
      if((numBytesRead = line.read(data, 0, bufferLengthInBytes)) == -1) { 
       break; 
      } 
      out.write(data, 0, numBytesRead); 
     } 

     // we reached the end of the stream. stop and close the line. 
     line.stop(); 
     line.close(); 
     line = null; 

     // stop and close the output stream 
     try { 
      out.flush(); 
      out.close(); 
     } catch (IOException ex) { 
      ex.printStackTrace(); 
     } 

     // load bytes into the audio input stream for playback 

     byte audioBytes[] = out.toByteArray(); 
     ByteArrayInputStream bais = new ByteArrayInputStream(audioBytes); 
     audioInputStream = new AudioInputStream(bais, format, audioBytes.length/frameSizeInBytes); 

     long milliseconds = (long)((audioInputStream.getFrameLength() * 1000)/format.getFrameRate()); 
     duration = milliseconds/1000.0; 

     try { 
      audioInputStream.reset(); 
     } catch (Exception ex) { 
      ex.printStackTrace(); 
      return; 
     } 

     samplingGraph.createWaveForm(audioBytes); 
    } 
} // End class Capture  

}

나는 그것을 통해 여러 번 갔다 오디오 값이 계산되는 곳 아래 부분이 있음을 알고 있지만 내 문제는 내가 그 시점 정보를 검색 할 수있는 방법 아무 생각이 없다는 것입니다있다 즉, 그 값은 어떤 시간 간격에 속해 있는가?

int frames_per_pixel = audioBytes.length/format.getFrameSize()/w; 
      byte my_byte = 0; 
      double y_last = 0; 
      int numChannels = format.getChannels(); 
      for (double x = 0; x < w && audioData != null; x++) { 
       int idx = (int) (frames_per_pixel * numChannels * x); 
       if (format.getSampleSizeInBits() == 8) { 
        my_byte = (byte) audioData[idx]; 
       } else { 
        my_byte = (byte) (128 * audioData[idx]/32768); 
       } 
       double y_new = (double) (h * (128 - my_byte)/256); 
       lines.add(new Line2D.Double(x, y_last, x, y_new)); 
       y_last = y_new; 
      } 

I는 JFreeChart를들 XYSeriesPLot를 사용하지만, 문제 × (시간) 및 Y (이 크기이지만,이 코드 y_new이다)의 요구 값을 계산하는 데 플롯 하시겠습니까?

나는 그것이 매우 쉬운 일이다 이해하지만이 모든 오디오 물건에 새로운 오전, 나는 오디오 파일 뒤에 이론을 이해하지만이 힘든 솔루션

enter link description here

답변

2

간단한 문제가 될 것 같다 실현해야 할 핵심 사항은 제공된 코드에서 플롯이 실제 오디오 데이터보다 훨씬 낮은 해상도로 예상된다는 것입니다. 가 enter image description here

묘화 코드 후 그래프에서 파란 박스와 같은 데이터를 나타내고, 예를 들어, 다음 파형을 고려 enter image description here

박스 1 화소 넓은 가진 라인이 대응 종단점 (x,y_last)(x,y_new). 보시다시피, 파형이 충분히 평탄 할 때 y_last에서 y_new까지의 진폭 범위는 상자 내의 샘플에 대한 공정한 근사치입니다.

이제 파형을 픽셀 단위로 렌더링하려는 경우 (래스터 디스플레이)이 표현이 편리 할 수 ​​있습니다. 그러나 XYplot 그래프 (jfreechart에서 볼 수 있듯이)는 (x,y) 점의 시퀀스 만 지정하면되고 XYPlot은 그 점 사이의 그리기 세그먼트를 처리합니다. 이것은 다음과 같은 그래프의 녹색 선에 해당하는 다음 XYPlot에 그대로 enter image description here

이론에서 방금 매 샘플 을 제공 할 수있다. 그러나 샘플이 거의 없으면 플롯하기에 상당히 무거워집니다. 따라서 일반적으로 데이터를 먼저 다운 샘플링합니다. 파형이 충분히 평활하면, 다운 샘플링 프로세스는 데시 메이션 (즉, N 샘플마다 1을 취함)으로 감소한다. 데시 메이션 인자 N은 렌더링 성능과 파형 근사 정밀도 사이의 균형을 제어합니다. 데시 메이션 팩터 frames_per_pixel이 제공된 코드에서 좋은 래스터 디스플레이를 생성하는 데 사용되는 경우 (예 : 보려는 파형 피쳐가 뭉툭한 픽셀 모양으로 숨겨져 있지 않고 앨리어싱 아티팩트가 표시되지 않는 곳) XYPlot에 대해서도 같은 요소가 여전히 충분해야합니다 (실제로 조금 더 다운 샘플링 할 수 있습니다).

샘플을 시간/진폭 축에 매핑하는 경우 제공된 플롯팅 코드에 정의 된대로 xy 매개 변수를 사용하지 않습니다. 이는 래스터 유형 디스플레이에 적용 할 수있는 픽셀 인덱스입니다. 위의 파란색 상자 표현입니다.)

대신 제공된 샘플 코드 (idx 제공된 코드)를 샘플링 속도로 나눔으로써 시간 축으로 직접 매핑 할 수 있습니다 (format.getFrameRate()에서 얻을 수 있음). 마찬가지로, audioData[idx] 샘플을 8 비트/샘플 데이터의 경우 128로, 16 비트/샘플 데이터의 경우 32768로 나누어 전체 샘플 값을 [-1,+1] 범위로 매핑합니다.

wh 매개 변수의 주요 목적은 플로팅 영역 크기를 구성하는 데 남아 있지만 더 이상 XYPlot 입력을 계산하는 데 직접 필요하지 않습니다. XYPlot 자체는 시간/진폭 값을 픽셀 좌표로 매핑합니다. 반면에 w 매개 변수는 그릴 지점 수를 결정하는 추가 목적으로도 사용되었습니다. 이제는 파형에 너무 많은 왜곡을 표시하지 않고 유지할 수있는 데시 메이션을 기준으로 포인트 수를 제어하거나 가능한 성능 플롯의 최대 해상도로 파형을 표시 할 수 있습니다 (일부 성능 비용 포함). w 샘플 미만의 파형을 표시하려는 경우 frames_per_pixel을 부동 소수점 값으로 변환해야 할 수도 있습니다.

+0

답변을 많이 주셔서 고마워요. jfreechart에이 질문에 대한 답을 줄 수있었습니다. 코드에서 x와 y의 값도 너비와 높이의 영향을 받았습니다 (변수 w & h) wouldn 이러한 것들이 오디오 정보에 영향을 미치지 않습니까? 그렇다면 어떻게 그 (것)들을 삭제할 수 있습니까? y 값을 계산하는 주 루프는 실제로 너비에 달려 있기 때문에 – Sudh

+0

@Sudh : width 및 height 매개 변수에 대한 업데이트를 참조하십시오. – SleuthEye