Java音频可视化器 - 如何捕获扬声器的实时声音输出?

Eri*_*ric 15 java audio javasound output

tl; dr对于未来的读者,使用Java或C#无法(现在)录制实时音频.使用C++,因为它提供了大量的音频api.


我的目标是获得在Windows机器上播放的当前声音,并像图形音频可视化器一样分析声音(获得音量属性和Hz(基数和高音)).当我说当前声音时,我的意思是如果要播放Youtube视频或Spotify歌曲,这个程序会读取该音频输出.我无意播放声音,但实时捕捉它并将其可视化.

在尝试这样做时,我阅读了如何构建音频波形显示,并探讨了如何将音频文件转换为字节数组(一行).这没有用,因为它不会得到当前的声音.我还阅读了如何捕获音频,以及这个java访问声音教程,这些都没有回答我的问题,因为它们都需要加载歌曲文件.

我根本就不理解这一点.我完全无能为力,任何帮助都会受到赞赏.

编辑:我做了一些更多的环顾四周,这个来源第二个答案让我得出结论:我可以找到所有的音频设备,看看哪一个产生声音.我不知道在那之后该怎么办.

编辑2(再次编辑):从实验和环顾四周,我在下面编写了这段代码.我认为这让我朝着我想要的方向前进,但我不知道如何完成它.

    Mixer.Info[] mixers = AudioSystem.getMixerInfo();
    for (Mixer.Info mixerInfo : mixers) {
        Mixer mixer = AudioSystem.getMixer(mixerInfo);
        try {
            mixer.open();
            Line.Info[] lines = mixer.getTargetLineInfo();
            for (Line.Info linfo : lines) {

                Line line = AudioSystem.getLine(linfo);

                //here I'm opening the line, but I don't know how to grab data
                line.open();

            }
        } catch (LineUnavailableException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
Run Code Online (Sandbox Code Playgroud)

我使用了这个来源:在混音器行中检查音频播放的级别,但我不打算检查所有播放音量的行,我只需要用户默认的混音器,获取该行,并且能够分析数据.

编辑3:我试过了:

    //creating a format for getting sound
    float sampleRate = 8000;
    int sampleSizeInBits = 16;
    int channels = 2;
    boolean signed = true;
    boolean bigEndian = true;
    AudioFormat format = new AudioFormat(sampleRate, sampleSizeInBits, channels, 
        signed, bigEndian);

    //creating a line based off of the format
    DataLine.Info info = new DataLine.Info( TargetDataLine.class, format);
    TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);

    //opening and starting that line
    line.open(format);
    line.start();

    while (conditionIsTrue){
        //here, I don't know what to put as the parameters.
        //Had I known, I don't know how I would get to analyze the data
        line.read();
    }
Run Code Online (Sandbox Code Playgroud)

我想我使用上面的代码在正确的道路上,但我不知道如何提取声音并找到bpm,base,treble等.

编辑4:这是一个有趣的读物:Java中的实时低延迟音频处理.这并不涉及哪些类以及如何实际实现它,但它提供了一些见解.

编辑5:@AndrewThompson使用基于链接的这段代码,我能够迭代可用的源和目标行.

Mixer.Info[] mixers = AudioSystem.getMixerInfo();
    for (Mixer.Info mixerInfo : mixers) {
        Mixer mixer = AudioSystem.getMixer(mixerInfo);
        try {
            mixer.open();
            Line.Info[] sourceLines = mixer.getSourceLineInfo();
            Line.Info[] targetLine = mixer.getTargetLineInfo();
            for (Line.Info sourceLinfo : sourceLines) {
                System.out.println(sourceLinfo );
            }
            for (Line.Info targetLinefo : targetLine) {
                System.out.println(targetLinefo);
            }

        } catch (LineUnavailableException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
Run Code Online (Sandbox Code Playgroud)

输出如下所示:

interface SourceDataLine supporting 8 audio formats, and buffers of at least 32 bytes
interface Clip supporting 8 audio formats, and buffers of at least 32 bytes
interface SourceDataLine supporting 8 audio formats, and buffers of at least 32 bytes
interface Clip supporting 8 audio formats, and buffers of at least 32 bytes
interface SourceDataLine supporting 8 audio formats, and buffers of at least 32 bytes
interface Clip supporting 8 audio formats, and buffers of at least 32 bytes
HEADPHONE target port
SPEAKER target port
Run Code Online (Sandbox Code Playgroud)

然后我创建了一个方法来获取所有行的声级,如下所示:

private static void getVolumeOfAllLines() {
    Mixer.Info[] mixers = AudioSystem.getMixerInfo();
    for (Mixer.Info mixerInfo : mixers) {
        Mixer mixer = AudioSystem.getMixer(mixerInfo);
        try {
            mixer.open();
            Line.Info[] lines = mixer.getSourceLineInfo();
            for (Line.Info linfo : lines) {
                DataLine line = (DataLine)AudioSystem.getLine(linfo);
                if(line != null)
                    System.out.println(line.getLevel());
            }
        } catch (LineUnavailableException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

-in试图找到当前播放的声音,表示音量较高.返回:

-1.0
-1.0
-1.0
-1.0
-1.0
-1.0
Run Code Online (Sandbox Code Playgroud)

没有进展.


新守则:

    private static void debug(){
    Mixer.Info[] mixers = AudioSystem.getMixerInfo();
    for (Mixer.Info mixerInfo : mixers) {
        Mixer mixer = AudioSystem.getMixer(mixerInfo);
        try {
            mixer.open();
            Line.Info[] lines = mixer.getTargetLineInfo();


            AudioFormat format = new AudioFormat(
                    AudioFormat.Encoding.PCM_SIGNED,
                    44100,
                    16, 2, 4,
                    44100, false);

            AudioFormat[] tdl = AudioSystem.getTargetFormats(AudioFormat.Encoding.PCM_SIGNED, format);

            for (Line.Info linfo : lines) {

                //Line line = AudioSystem.getLine(linfo);


                TargetDataLine line = null;
                DataLine.Info info = new DataLine.Info(TargetDataLine.class,
                        format); // format is an AudioFormat object
                if (!AudioSystem.isLineSupported(info))
                {
                    System.out.println("line not supported:" + line );
                }

                try
                {
                    line = (TargetDataLine) AudioSystem.getLine(info); //error
                    line.open(format);
                    System.out.println("line opened:" + line);

                    line.start();

                    byte[] buffer = new byte[1024];
                    int ii = 0;
                    int numBytesRead = 0;
                    while (ii++ < 100) {
                        // Read the next chunk of data from the TargetDataLine.
                        numBytesRead =  line.read(buffer, 0, buffer.length);

                        System.out.println("\nnumBytesRead:" + numBytesRead);
                        if (numBytesRead == 0) continue;
                        // following is a quickie test to see if content is only 0 vals
                        // present in the data that was read.

                        for (int i = 0; i < 16; i++)
                        {
                            if (buffer[i] != 0)
                                System.out.print(".");
                            else
                                System.out.print("0");
                        }
                    }

                } catch (LineUnavailableException ex) {
                    ex.printStackTrace();
                    //...
                }
            }
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Phi*_*ner 6

Java 教程中有一个很好的示例,可以帮助您从一行中提取 PCM 数据。在标题为使用文件和格式转换器的教程中,标题为“读取声音文件”的部分下有一个代码示例。相关部分是“片段”示例,并由代码标记:

  // Here, do something useful with the audio data that's 
  // now in the audioBytes array...
Run Code Online (Sandbox Code Playgroud)

此时,您可以访问该行的各个字节,并且可以根据声音文件的格式将它们组合成 PCM。还有其他几个stackoverflow问题,它们处理从字节到PCM的细节。


我正在添加一些代码以响应评论。

由于无法转换为 TargetDataLine,从教程中提取的以下内容允许我将一行转换为 TargetDataLine。

    AudioFormat format = new AudioFormat(
        AudioFormat.Encoding.PCM_SIGNED, 
        44100,
        16, 2, 4, 
        44100, false);

    TargetDataLine line = null;
    DataLine.Info info = new DataLine.Info(TargetDataLine.class, 
        format); // format is an AudioFormat object
    if (!AudioSystem.isLineSupported(info)) 
    {
        System.out.println("line not supported:" + line );
    }

    try 
    {
        line = (TargetDataLine) AudioSystem.getLine(info);
        line.open(format);
        System.out.println("line opened:" + line);

        line.start();

        byte[] buffer = new byte[1024];
        int ii = 0;
        int numBytesRead = 0;
        while (ii++ < 100) {
       // Read the next chunk of data from the TargetDataLine.
            numBytesRead =  line.read(buffer, 0, buffer.length);

            System.out.println("\nnumBytesRead:" + numBytesRead);
               if (numBytesRead == 0) continue;
     // following is a quickie test to see if content is only 0 vals
    // present in the data that was read.

               for (int i = 0; i < 16; i++)
               {
                   if (buffer[i] != 0)
                       System.out.print(".");
                   else
                       System.out.print("0");
               }
            }

        } catch (LineUnavailableException ex) {
            ex.printStackTrace();
        //... 
    }
}
Run Code Online (Sandbox Code Playgroud)

但我只是使用 CD 质量格式方案抓取一条线,我没有试图找出哪条线具有正在播放的 YouTube 频道的声音。


OP 和我去聊天并继续对此进行破解,但无法找到解决方案。似乎许多其他人也看到了这一点并放弃了。我希望赏金很诱人——这是一个有趣的问题。