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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26574326/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 10:10:38  来源:igfitidea点击:

How to calculate the level/amplitude/db of audio signal in java?

javaaudiojavasoundjavax.sound.sampled

提问by AliceInChains

I want to create a audio level meter in java for the microphone to check how loud the input is. It should look like the one of the OS. I'm not asking about the gui. It is just about calculating the audio level out of the bytestream produced by

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

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

So I already have something that is running, but it is not even close to the levelmeter of my OS (windows) It stucks in the middle. I have values between 0 and 100 that is good but in the middle volume it stucks around 60 no matter how loud the input is.

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

This is how I calculate it now:

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

            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;

Is there a better/more precise way to calculate the audio level to monitor it? Or did I maybe do a major mistake?

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

That is my 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);
}

回答by Radiodef

Principally the problem seems to be that you are reading the audio data incorrectly.

主要问题似乎是您读取的音频数据不正确。

Specifically I'm not really sure what this excerpt is supposed to mean:

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

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

But anyhow since you are recording 16-bit data the bytes in the byte array aren't meaningful on their own. Each byte only represents 1/2 of the bits in each sample. You need to 'unpack' them to int, float, whatever, before you can do anything with them. For raw LPCM, concatenating the bytes is done by shifting them and ORing them together.

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

Here is an MCVE to demonstrate a rudimentary level meter (both RMS and simple peak hold) in Java.

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

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

Note the format conversion is hard-coded there.

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

You may also see "How do I use audio sample data from Java Sound?"for my detailed explanation of how to unpack audio data from the raw bytes.

您可能还会看到“如何使用来自 Java Sound 的音频样本数据?” 有关如何从原始字节解包音频数据的详细说明。



Related:

有关的:

回答by David

The above code will find the data point with highest value but cannot determine the peak value of the reconstructed data samples. To find the reconstructed peak you would have to pass the data samples through a low pass filter. or use a DFT/FFT algorithm.

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