2014-10-26 8 views
5

Voglio creare un misuratore di livello audio in java per il microfono per verificare quanto è forte l'input. Dovrebbe assomigliare a quello del sistema operativo. Non sto chiedendo la gui. Si tratta solo di calcolare il livello di uscita audio del bytestream prodotto daCome calcolare il livello/ampiezza/db del segnale audio in java?

n = targetDataLine.read(tempBuffer , 0 , tempBuffer.length); 

Così ho già qualcosa che è in esecuzione, ma non è nemmeno vicino alla levelmeter del mio sistema operativo (Windows) It stucks nel mezzo . Ho dei valori tra 0 e 100 che sono buoni, ma nel mezzo del volume spicca intorno al 60, non importa quanto sia forte l'input.

Ecco come ho calcolare subito:

  amplitude = 0; 
     for (int j = 0; j < tempBuffer.length; j = j +2){ 
      if (tempBuffer[j] > tempBuffer[j+1]) 
       amplitude = amplitude + tempBuffer[j] - tempBuffer[j+1]; 
      else amplitude = amplitude + tempBuffer[j + 1] - tempBuffer[j]; 
     } 
     amplitude = amplitude/tempBuffer.length * 2; 

C'è un/modo migliore più preciso per calcolare il livello audio per monitorare esso? O forse ho fatto un grave errore?

Questo è il mio Formato audio:

public static AudioFormat getAudioFormat(){ 
    float sampleRate = 20000.0F; 
    //8000,11025,16000,22050,44100 
    int sampleSizeInBits = 16; 
    //8,16 
    int channels = 1; 
    //1,2 
    boolean signed = true; 
    //true,false 
    boolean bigEndian = false; 
    //true,false 
    return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian); 
    //return new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000.0F, 8, 1, 1, 8000.0F, false); 
} 
+0

Non capisco come si intende che il calcolo funzioni. (Stavi solo indovinando come decomprimere l'audio?) Cos'è il formato audio di TargetDataLine? – Radiodef

+0

Ho esaminato la matrice di byte prodotta da n = targetDataLine.read (tempBuffer, 0, tempBuffer.length); e ho visto che si alterna tra valori bassi e alti, quindi ho pensato che fosse l'ampiezza. In realtà misuro veramente il livello di input ma funziona perché è proporzionale.L'unica cosa negativa è che nella gamma normale si discosta solo un po 'e non è così preciso come il livello di Windows dello stesso input. Forse qualcuno ha ulteriori consigli su come farlo? – AliceInChains

risposta

13

Principalmente il problema sembra essere che si sta leggendo i dati audio in modo non corretto.

In particolare io non sono davvero sicuro di quello che si suppone questo estratto a significare:

if (tempBuffer[j] > tempBuffer[j+1]) 
    ... tempBuffer[j] - tempBuffer[j+1]; 
else 
    ... tempBuffer[j + 1] - tempBuffer[j]; 

Ma tant'è dal momento che si sta registrando dati a 16 bit i byte nella matrice di byte non sono significativi per conto proprio. Ogni byte rappresenta solo 1/2 dei bit in ciascun campione. Devi "decomprimerli" su int, float, qualunque cosa, prima di poter fare qualsiasi cosa con loro. Per l'LPCM grezzo, la concatenazione dei byte viene eseguita spostandoli e ordinandoli insieme.

Ecco un MCVE per dimostrare un misuratore di livello rudimentale (sia RMS che semplice tenuta di picco) in Java.

Meter

import javax.swing.SwingUtilities; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.JComponent; 

import java.awt.BorderLayout; 
import java.awt.Graphics; 
import java.awt.Color; 
import java.awt.Dimension; 
import javax.swing.border.EmptyBorder; 

import javax.sound.sampled.AudioFormat; 
import javax.sound.sampled.TargetDataLine; 
import javax.sound.sampled.AudioSystem; 
import javax.sound.sampled.LineUnavailableException; 

public class LevelMeter extends JComponent { 
    private int meterWidth = 10; 

    private float amp = 0f; 
    private float peak = 0f; 

    public void setAmplitude(float amp) { 
     this.amp = Math.abs(amp); 
     repaint(); 
    } 

    public void setPeak(float peak) { 
     this.peak = Math.abs(peak); 
     repaint(); 
    } 

    public void setMeterWidth(int meterWidth) { 
     this.meterWidth = meterWidth; 
    } 

    @Override 
    protected void paintComponent(Graphics g) { 
     int w = Math.min(meterWidth, getWidth()); 
     int h = getHeight(); 
     int x = getWidth()/2 - w/2; 
     int y = 0; 

     g.setColor(Color.LIGHT_GRAY); 
     g.fillRect(x, y, w, h); 

     g.setColor(Color.BLACK); 
     g.drawRect(x, y, w - 1, h - 1); 

     int a = Math.round(amp * (h - 2)); 
     g.setColor(Color.GREEN); 
     g.fillRect(x + 1, y + h - 1 - a, w - 2, a); 

     int p = Math.round(peak * (h - 2)); 
     g.setColor(Color.RED); 
     g.drawLine(x + 1, y + h - 1 - p, x + w - 1, y + h - 1 - p); 
    } 

    @Override 
    public Dimension getMinimumSize() { 
     Dimension min = super.getMinimumSize(); 
     if(min.width < meterWidth) 
      min.width = meterWidth; 
     if(min.height < meterWidth) 
      min.height = meterWidth; 
     return min; 
    } 

    @Override 
    public Dimension getPreferredSize() { 
     Dimension pref = super.getPreferredSize(); 
     pref.width = meterWidth; 
     return pref; 
    } 

    @Override 
    public void setPreferredSize(Dimension pref) { 
     super.setPreferredSize(pref); 
     setMeterWidth(pref.width); 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       JFrame frame = new JFrame("Meter"); 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

       JPanel content = new JPanel(new BorderLayout()); 
       content.setBorder(new EmptyBorder(25, 50, 25, 50)); 

       LevelMeter meter = new LevelMeter(); 
       meter.setPreferredSize(new Dimension(9, 100)); 
       content.add(meter, BorderLayout.CENTER); 

       frame.setContentPane(content); 
       frame.pack(); 
       frame.setLocationRelativeTo(null); 
       frame.setVisible(true); 

       new Thread(new Recorder(meter)).start(); 
      } 
     }); 
    } 

    static class Recorder implements Runnable { 
     final LevelMeter meter; 

     Recorder(final LevelMeter meter) { 
      this.meter = meter; 
     } 

     @Override 
     public void run() { 
      AudioFormat fmt = new AudioFormat(44100f, 16, 1, true, false); 
      final int bufferByteSize = 2048; 

      TargetDataLine line; 
      try { 
       line = AudioSystem.getTargetDataLine(fmt); 
       line.open(fmt, bufferByteSize); 
      } catch(LineUnavailableException e) { 
       System.err.println(e); 
       return; 
      } 

      byte[] buf = new byte[bufferByteSize]; 
      float[] samples = new float[bufferByteSize/2]; 

      float lastPeak = 0f; 

      line.start(); 
      for(int b; (b = line.read(buf, 0, buf.length)) > -1;) { 

       // convert bytes to samples here 
       for(int i = 0, s = 0; i < b;) { 
        int sample = 0; 

        sample |= buf[i++] & 0xFF; // (reverse these two lines 
        sample |= buf[i++] << 8; // if the format is big endian) 

        // normalize to range of +/-1.0f 
        samples[s++] = sample/32768f; 
       } 

       float rms = 0f; 
       float peak = 0f; 
       for(float sample : samples) { 

        float abs = Math.abs(sample); 
        if(abs > peak) { 
         peak = abs; 
        } 

        rms += sample * sample; 
       } 

       rms = (float)Math.sqrt(rms/samples.length); 

       if(lastPeak > peak) { 
        peak = lastPeak * 0.875f; 
       } 

       lastPeak = peak; 

       setMeterOnEDT(rms, peak); 
      } 
     } 

     void setMeterOnEDT(final float rms, final float peak) { 
      SwingUtilities.invokeLater(new Runnable() { 
       @Override 
       public void run() { 
        meter.setAmplitude(rms); 
        meter.setPeak(peak); 
       } 
      }); 
     } 
    } 
} 

Nota la conversione del formato è hard-coded lì.

È inoltre possibile vedere "How do I use audio sample data from Java Sound?" per la mia spiegazione dettagliata su come decomprimere i dati audio dai byte non elaborati.


correlati:

+0

Thx !!! Con un piccolo adattamento ha funzionato bene, fantastico;) – AliceInChains

+1

Grande spiegazione! Una domanda, buf è in relazione con un canale mono, o è un dato stereo L + R? – Billyjoker

+0

@Billyjoker È mono. Il numero di canali è fornito negli argomenti del costruttore su 'AudioFormat' (nella prima riga del metodo' run'). – Radiodef

0

Il codice di cui sopra si trova il punto dati con il valore più alto, ma non può determinare il valore di picco dei campioni di dati ricostruiti. Per trovare il picco ricostruito dovresti passare i campioni di dati attraverso un filtro passa-basso. o utilizzare un algoritmo DFT/FFT.