如何计算java中音频信号的电平/幅度/ db?

我想在java中为麦克风创建一个音频电平表,以检查输入的大小。 它应该看起来像操作系统之一。 我不是在询问gui。 它只是计算出来的字节流中的音频电平

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

所以我已经有了一些正在运行的东西,但它甚至都没有接近我操作系统的水平仪(窗口)它卡在中间。 我有0到100之间的值是好的,但是在中间音量,无论输入多么大,它都会在60左右。

这是我现在计算它的方式:

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

是否有更好/更精确的方法来计算音频电平来监控它? 或者我可能犯了一个重大错误?

那是我的Audioformat:

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

主要问题似乎是您正在错误地读取音频数据。

具体来说,我不确定这段摘录应该是什么意思:

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

但无论如何,由于您正在记录16位数据,因此字节数组中的字节本身没有意义。 每个字节仅代表每个样本中的1/2位。 在你可以对它们做任何事情之前,你需要将它们“解包”到int,float等等。 对于原始LPCM,连接字节是通过移位它们并将它们“或”在一起来完成的。

这是一个MCVE,用于演示Java中的基本电平表(RMS和简单峰值保持)。

仪表

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

请注意,格式转换在那里是硬编码的。

您还可以看到“如何使用Java Sound中的音频样本数据?” 我详细解释了如何从原始字节解压缩音频数据。


有关:

  • 如何跟踪音频播放位置?
  • 如何使波形渲染更有趣?

上述代码将找到具有最高值的数据点,但不能确定重建数据样本的峰值。 要找到重建的峰值,您必须通过低通滤波器传递数据样本。 或使用DFT / FFT算法。