16

tl;dr For future readers, recording real-time audio is not possible (for now) with Java or C#. Use C++, as it provides a plethora of audio api's.


My goal is to get the current sound played on a Windows machine, and analyze the sound much like a graphic audio visualizer does (get the volume property and Hz(base and treble)). When I say current sound, I mean if one was to play a Youtube video or Spotify song, and this program would read that audio output. I have NO intention to play sound, but capture it in real-time and visualize it.

In attempting to do so, I read on how to build an audio waveform display and it touches on how to convert an audio file to an array of bytes (a Line). This doesn't help because It wont get the current sound. I also read on how to capture audio as well, and this java accessing sound tutorial, neither of those answer my question because they both require a song file to be loaded.

I'm just not understanding this at all. I'm totally clueless, and any help would be appreciated.

Edit: I did a little more looking around, and the second answer from this source lead me to the conclusion that: I could find all of the audio devices, see which one is producing sound. I don't know what to do after that.

Edit 2 (edited again): From experimenting and looking around, I wrote this code below. I think this gets me in the direction I'm wanting, but I don't know how to finish it.

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

I used this source: Checking The Level of Audio-Playback in a mixers line, but I'm not looking to check for all the lines that are playing volume, I just need the users default Mixer, get that Line, and be able to analyze the data.

Edit 3: I have tried:

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

I think I'm on the right path using the code above, but I don't know how to extract the sound and find the bpm, base, treble, etc.

Edit 4: This was an interesting read : Real-time low latency audio processing in Java. This doesn't touch on what classes and how to actually implement this, but it provides some insight.

Edit 5: @AndrewThompson Using this piece of code based off of your link I was able to iterate over the available source and target lines.

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

The output looks like this:

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

I have then created a method that gets the sound levels of all the lines which looks like this:

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 attemps to find the current line playing sound, indicating a higher volume. This returns:

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

No progress.


New Code:

    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();
        }
    }
}
Community
  • 1
  • 1
Eric
  • 444
  • 7
  • 19
  • I am not sure it is always possible. *Some* computers have a "Stereo Mix" input which acts like a microphone recording the output. – user253751 Aug 09 '16 at 04:46
  • When you say some computers, could you elaborate? (As in only windows machines, mac, etc) – Eric Aug 09 '16 at 04:48
  • I've seen it on some Windows machines. – user253751 Aug 09 '16 at 04:58
  • I really can't see why this deserves a -1 – Eric Aug 09 '16 at 18:25
  • Does anyone know another site that would better fit this post? – Eric Aug 10 '16 at 03:01
  • You need to extract the Amplitudes and then do lots of frequency domain (not sure its possible in time domain) analysis to achieve what you want.. – gtiwari333 Aug 10 '16 at 03:13
  • 1
    Take a look here : http://ganeshtiwaridotcomdotnp.blogspot.com/search/label/Audio%20Processing on how to extract PCM array from recorded audio/saved .wave file – gtiwari333 Aug 10 '16 at 03:15
  • 3
    Thanks for the link. Though it is helpful, I'm looking for a way to get real-time audio, not an audio file stored in .wmv, mp3, etc. I appreciate the help! – Eric Aug 10 '16 at 03:19
  • the links I posted has logic to deal with real time audio – gtiwari333 Aug 10 '16 at 03:26
  • 1
    Look at https://github.com/gtiwari333/speech-recognition-java-hidden-markov-model-vq-mfcc/blob/master/SpeechRecognitionHMM/src/org/ioe/tprsa/audio/JSoundCapture.java for specific code ! You can play around this class and add logic to process the audio real time ! – gtiwari333 Aug 10 '16 at 03:27
  • 1
    *"How do I get the current sound output and its properties?"* Start by iterating the available sound lines using the `MediaTypes` source seen in [this answer](http://stackoverflow.com/a/7616206/418556).. Note, as mentioned up-thread, some PCs / OS' / versions of OS / sound cards will allow Java to tap into the playing sound, others won't. It is a very hit and miss affair. – Andrew Thompson Aug 10 '16 at 08:50
  • "For future readers, recording real-time audio is not possible (for now) with Java ": who are you?? – gpasch Aug 15 '16 at 18:07
  • If you target windows 10 and want to access / record the system sound you can make use of standard javax.sound API. Just enable Stereo Mix first: https://www.howtogeek.com/howto/39532/how-to-enable-stereo-mix-in-windows-7-to-record-audio/ and use this mixer in code. – Oliver Oct 07 '20 at 18:24

3 Answers3

6

There is a good example from the Java tutorials that will help you to extract the PCM data from a line. In the tutorial titled Using Files and Format Converters there is a code example under the section heading "Reading Sound Files". The relevant portion is the "snippet" example, and is marked by the code:

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

At this point, you hve access to the individual bytes of the line, and can take and assemble them into PCM according to the format of the sound file. There are several other stackoverflow questions which deal with the specifics of going to and from bytes to PCM.


Am adding some code in response to comments.

Per not being able to cast to TargetDataLine, the following, extracted from the tutorial, allowed me to cast a line to 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();
        //... 
    }
}

But I'm just grabbing a line using a CD quality format scheme, I haven't tried to figure out which line has the sound from the YouTube channel that is playing.


OP and I went to chat and continued to hack on this, but were unable to work through to a solution. Seems many others have looked at this and given up, too. I'm hoping the bounty proves tempting -- this is an interesting issue.

Phil Freihofner
  • 7,645
  • 1
  • 20
  • 41
  • The issue with this is "In either case, you need to get access to the data contained in the audio file". I'm looking to extract real time audio sent out by a line. The methods on the Java tutorials deal with pre-existing sound files. – Eric Aug 11 '16 at 03:10
  • I am suspicious of the method .getLevel(). There have been instances where attempting to do something with the level of an active line failed entirely, so I always work at the byte/DSP level as described in my post. I mostly deal with live generation via synths, so I am working with real time lines not existing files. I just haven't done much with incoming live sound. But the tutorials do describe how to identify the lines (and use of TargetDataLine), and the routine I mentioned can be used to test if there is actually data flowing and not just a string of 0's, and in real time. – Phil Freihofner Aug 12 '16 at 00:30
  • After reading the tutorials, TargetDataLine() allows capture of incoming audio from recording ports. What I can't figure out is how to assign a line-in port and create a TargetDataLine that a Port can control. – Eric Aug 12 '16 at 00:36
  • 1
    I was reacting mostly to the point in your question where you have a line and wrote the comment "// here I'm opening the line but I don't know how to grab the data". The section I referred you to does just this, though you might have to rewrite the example to use the correct types of lines for inputs. Sorry I can't be more helpful with a working example, as I haven't done things like microphone monitoring or other inputting. Just got your reply of seconds ago, will take another look at relevant section in tutorials and see what I can figure out. – Phil Freihofner Aug 12 '16 at 00:36
  • From your first example, after opening the line, have you tried reading from it? You can just toss the data into a buffer array and look to see if the values are 0 or not, for starters. Also, if the lines or ports are loaded into an array, it seems you should be able to select from that array and open and read and inspect. Doing so while running a YouTube and not doing anything else with sound might be part of the test. – Phil Freihofner Aug 12 '16 at 00:44
  • The issue with that is in order to read from that Line, I need to cast it into a TargetDataLine to grab anything at all. In attempts to do so, I get `MixerPort cannot be cast to javax.sound.sampled.TargetDataLine` error. The same type of error applies when trying to cast to `SourceDataLine` or even `DataLine`. Casting to a `Port` works, but I can't extract any data from that. – Eric Aug 12 '16 at 00:52
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/120752/discussion-between-phil-freihofner-and-eric). – Phil Freihofner Aug 12 '16 at 01:26
2

There is no good solution using java. It's better to use jni to access different os hardware.

In windows, NAudio is a good choice. I follow its demo-Record Soundcard Output with WasapiLoopbackCapture ,compile a console exe and used it in Runtime.getRuntime().exec

gogog
  • 410
  • 5
  • 9
1

The thing that worked for me was to use Virtual Audio Cable. It creates a virtual input output duo where the output feeds the audio data to input and then you can easily get the data using Target data Line. Nothing else worked since it's a hardware limitation not software so we have to emulate virtual hardware in order to capture the output in realtime.

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

Awais
  • 36
  • 6