【问题标题】:Java audio visualizer- How to capture real-time sound output to speaker?Java 音频可视化器 - 如何捕获到扬声器的实时声音输出?
【发布时间】:2016-12-14 23:58:28
【问题描述】:

tl;dr 对于未来的读者,使用 Java 或 C#不可能(目前)录制实时音频。使用 C++,因为它提供了大量的音频 api。


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

在尝试这样做时,我阅读了如何build an audio waveform display 并涉及如何将音频文件转换为字节数组(一行)。这无济于事,因为它不会得到当前的声音。我还阅读了如何capture audiothis java accessing sound tutorial,它们都没有回答我的问题,因为它们都需要加载歌曲文件。

我根本不明白这一点。我完全一无所知,任何帮助将不胜感激。

编辑:我环顾四周,second answer from this source 让我得出结论:我可以找到所有的音频设备,看看哪个正在发出声音。我不知道那之后该怎么办。

编辑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();
        }

我使用了这个来源:Checking The Level of Audio-Playback in a mixers line,但我不想检查所有正在播放音量的线路,我只需要用户默认的混音器,获取该线路,并能够分析数据。

编辑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();
    }

我认为我使用上面的代码走在正确的道路上,但我不知道如何提取声音并找到 bpm、低音、高音等。

编辑 4:这是一篇有趣的文章:Real-time low latency audio processing in 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();
        }
    }

输出如下:

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

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

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

-in 尝试查找当前行播放的声音,表示音量较高。这返回:

-1.0
-1.0
-1.0
-1.0
-1.0
-1.0

没有进展。


新代码:

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

【问题讨论】:

  • 我不确定这是否总是可行的。 一些计算机具有“立体声混音”输入,其作用类似于记录输出的麦克风。
  • 看看这里:ganeshtiwaridotcomdotnp.blogspot.com/search/label/… 如何从录制的音频/保存的 .wave 文件中提取 PCM 阵列
  • 感谢您的链接。虽然它很有帮助,但我正在寻找一种获取实时音频的方法,而不是存储在 .wmv、mp3 等中的音频文件。感谢您的帮助!
  • 具体代码看github.com/gtiwari333/…!你可以玩这个类并添加逻辑来实时处理音频!
  • "我如何获得当前的声音输出及其属性?" 首先使用MediaTypes 中的MediaTypes 源迭代可用的声音线this answer.. 注意,如前所述,某些 PC / 操作系统 / 版本的操作系统 / 声卡将允许 Java 进入播放声音,而其他则不会。这是一个非常偶然的事件。

标签: java audio output javasound


【解决方案1】:

Java 教程中有一个很好的示例,可以帮助您从一行中提取 PCM 数据。在标题为Using Files and Format Converters 的教程中,在标题为“读取声音文件”的部分下有一个代码示例。相关部分是“sn-p”示例,并由代码标记:

  // Here, do something useful with the audio data that's 
  // now in the audioBytes array...

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


我正在添加一些代码来响应 cmets。

由于无法转换为 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();
        //... 
    }
}

但我只是使用 CD 质量格式方案抓取一行,我没有尝试弄清楚哪一行有来自正在播放的 YouTube 频道的声音。


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

【讨论】:

  • 这个问题是“在任何一种情况下,您都需要访问音频文件中包含的数据”。我正在寻找通过线路发送的实时音频。 Java 教程中的方法处理预先存在的声音文件。
  • 我对 .getLevel() 方法持怀疑态度。在某些情况下,尝试使用活动线的级别完全失败,所以我总是在我的帖子中描述的字节/DSP级别工作。我主要通过合成器处理实时生成,所以我使用的是实时线而不是现有文件。我只是没有对传入的现场声音做太多事情。但教程确实描述了如何识别线(以及 TargetDataLine 的使用),并且我提到的例程可用于实时测试是否有数据流动,而不仅仅是一串 0。
  • 阅读教程后,TargetDataLine() 允许从录制端口捕获传入的音频。我想不通的是如何分配一个输入端口并创建一个端口可以控制的 TargetDataLine。
  • 我的反应主要是你的问题中你有一条线并写了评论“//在这里我打开了这条线,但我不知道如何获取数据”。我提到的部分就是这样做的,尽管您可能必须重写示例以使用正确类型的行作为输入。抱歉,我不能对一个工作示例更有帮助,因为我没有做过麦克风监控或其他输入之类的事情。几秒钟前刚收到您的回复,将再次查看教程中的相关部分,看看我能弄清楚什么。
  • 从您的第一个示例中,打开线路后,您是否尝试从中读取?对于初学者,您可以将数据扔到缓冲区数组中,然后查看值是否为 0。此外,如果将线路或端口加载到数组中,您似乎应该能够从该数组中进行选择并打开并读取和检查。在运行 YouTube 时这样做并且不对声音做任何其他事情可能是测试的一部分。
【解决方案2】:

使用 java 没有好的解决方案。最好使用jni来访问不同的os硬件。

在 Windows 中,NAudio 是一个不错的选择。我按照它的demo-Record Soundcard Output with WasapiLoopbackCapture,编译一个控制台exe并在Runtime.getRuntime().exec中使用它

【讨论】:

    【解决方案3】:

    对我有用的是使用虚拟音频线。它创建了一个虚拟输入输出二重奏,其中输出将音频数据馈送到输入,然后您可以使用 Target data Line 轻松获取数据。没有其他方法起作用,因为这是硬件限制而不是软件,因此我们必须模拟虚拟硬件才能实时捕获输出。

    https://vb-audio.com/Cable/

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-04
      • 2012-08-27
      • 2014-01-10
      • 1970-01-01
      • 2017-12-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多