【问题标题】:How to calculate the level/amplitude/db of audio signal in java?如何在java中计算音频信号的电平/幅度/db?
【发布时间】:2014-12-21 20:24:30
【问题描述】:

我想在 java 中为麦克风创建一个音频电平表,以检查输入的音量。它应该看起来像操作系统之一。我不是在问gui。它只是从

产生的字节流中计算音频电平
n = targetDataLine.read( tempBuffer , 0 , tempBuffer.length );

所以我已经有一些正在运行的东西,但它甚至不接近我的操作系统(Windows)的电平表它卡在中间。我的值介于 0 到 100 之间,这很好,但在中等音量下,无论输入多么响亮,它都会停留在 60 左右。

这是我现在的计算方式:

            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;

是否有更好/更精确的方法来计算音频电平以进行监控?还是我可能犯了重大错误?

那是我的音频格式:

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

【问题讨论】:

  • 我不明白你的计算是如何工作的。 (你只是在猜测如何解压音频?) TargetDataLine 的 AudioFormat 是什么?
  • 我查看了由 n = targetDataLine.read( tempBuffer , 0 , tempBuffer.length ); 生成的字节数组;并看到它在低值和高值之间交替,所以我认为这是幅度。事实上,我确实测量了输入的水平,但它之所以有效,是因为它是成比例的。唯一不利的是,在正常范围内它只会有一点偏差,并且不如相同输入的窗口级别那么精确。也许有人有进一步的建议如何做到这一点?

标签: java audio javasound javax.sound.sampled


【解决方案1】:

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

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

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

但无论如何,由于您记录的是 16 位数据,因此字节数组中的字节本身没有意义。每个字节仅代表每个样本中的 1/2 位。您需要先将它们“解包”为 int、float 等,然后才能对它们进行任何操作。对于原始 LPCM,连接字节是通过将它们移位和 ORing 一起完成的。

这是一个 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);
                }
            });
        }
    }
}

注意格式转换是硬编码的。

您还可以查看"How do I use audio sample data from Java Sound?",了解我对如何从原始字节中解压缩音频数据的详细说明。


相关:

【讨论】:

  • 很好的解释!一个问题,buf与单声道有关,还是立体声L+R数据?
  • @Billyjoker 这是单声道的。通道数在AudioFormat 的构造函数参数中提供(在run 方法的第一行)。
  • 谢谢@Radiodef。在尝试使代码适应我的应用程序,而不是将值传递给 LevelMeter 时,我想表示 [0-127] 之间的幅度,但我有一些随机数据,与音量无关...... :(
  • @Billyjoker 我的简短示例中的转换代码(从原始字节到浮点样本)是硬编码的,因此它仅适用于 44.1kHZ、16 位、小端音频。 (这与 CD 质量的 WAV 文件相同。)如果您有不同格式的音频,则转换代码将不起作用。这就是我能想到的。我在this Q&A here 中解释了该代码的工作原理。我假设也有图书馆可以做到这一点。如果您使用与我在这里相同的代码从麦克风读取,那么我不确定问题是什么。
  • @Billyjoker 好吧,不幸的是,我认为我无能为力,因为问题似乎与我的代码无关。我对它进行了修改,它似乎在我的最终工作正常。但是,如果这有帮助,则 -32640 是值 0b11111111111111111000000010000000,如果缓冲区包含一对字节 -127, -127(或 128, 128,如果我们认为它们无符号)。如果缓冲区确实有 0,那么在我的转换代码之后你也会得到 0。我会说要么是你的输入系统(麦克风、声音设备等)有问题,要么是代码在某些方面与我的不同。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2020-07-23
    • 2018-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多