A couple questions before I even attempt this project...

Is it possible to create a VoIP application in Java?
Is it posssibe to do this using standard libraries?
If you can't make one with standard libraries, which libraries are needed to accomplish this?

Also, is this a good Introduction to Server and Networking in Java or should I start elsewhere?

Thanks for the help.

Recommended Answers

All 34 Replies

In theory you shoud be able to capture the mic input stream somehow, then compress it into byte arrays and send those over a socket conection, but honestly I think you will find it very hard. (I think I would find it very hard.)
There may be libraries/packages you could use - Goodle's code repo would be a good place to look, also SourceForge.

ps Did you finish the Sudoku project?

I might give it a shot. I have done some research on this myself and I came across something called an SIP library or something like that.
I would rather try something native first.

Not yet, (thats why the thread is still open) gunna try working on it a bit today before I start this most likely. I think I can do it your way.

EDIT: Nvm, I can't do it your way :P

SIP (Session INitiation Protocol, or something like that) is a standard way to establish a 2-way "live" link between two machines, and is how standrad VOIP is set up. Skype etc use proprietaty equivalents

Alright, I want to basically have a "group call" similar to Ventrilo.

I want to attempt this using standard libraries first and I want to give it a shot regardless of my expeirence level. I have you guys to help me out with understanding it when I get stuck anyway!

Where should I start? :P

Good luck!
I would start by trying to spool sound to a file in real time, (we know playing it back isn't so hard now). Then you can replace the file stream by a socket connection to stream the same content to another machine. Establishing the initial connections is a separate problem. Just do one at a time.

Why not just send the audio right over the stream? Of course after compressing it which is another thing.

Only because it's not going to be easy, and it's a smart idea to break complex code down and solve one problem at a time. But if you're happy debugging your sockets across multiple machines or sessions at the same time as trying to figure out why your audio is not arriving or is all snafu'd up, go for it!

So basically, record it, then send it? I'm trying to think of how you would record it to a file, and send the file in real time.

That's not what I meant
First, solve the problem of writing audio to a stream in real time. Just use a file stream because (a) its easy and (b) you can easily play back the file to see if its working OK.
Once it's working, discard the 3 lines of code that refer to the file stream, and replace them with a socket stream.

Divide and conquer!

Hmm this is something really interesting to attemp tbh, james's idea is nice as well, first make sure that you can record voice and save it in a file. Once that is done, just replace instead of save to a file use sockets to send it. I'll bookmark this thread, seems really fun good luck if you try to attempt it

Ran into a frustrating issue that SHOULD be easy to solve.
File file = new File(TeamVoIP.class.getResource("/Resources/Servers.txt").toString()); crashes with an NPE.

You may find it easier to read that file by simply opening an InputStream directly from the resource, eg

InputStream is = getClass().getResourceAsStream("my file path");

Still getting an NPE.

Line 98: InputStream is = getClass().getResourceAsStream("/Resources/Servers.txt");
Line 99: System.out.println(is.toString());
Line 100: InputStreamReader reader = new InputStreamReader(is);

Error:

Exception in thread "main" java.lang.NullPointerException
    at com.geodox.teamvoip.main.ServerOptions.populateServers(ServerOptions.java:99)
    at com.geodox.teamvoip.main.ServerOptions.initGUI(ServerOptions.java:53)
    at com.geodox.teamvoip.main.ServerOptions.<init>(ServerOptions.java:37)
    at com.geodox.teamvoip.main.TeamVoIP.<init>(TeamVoIP.java:31)
    at com.geodox.teamvoip.main.TeamVoIP.main(TeamVoIP.java:25)

The NPE error is still unresolved if anyone has some idea's as to what exactly is throwing the NPE.

According to what you have already posted it's the line

System.out.println(is.toString());

System and System.out won't be null, so it has to be is
That var is set in the previous line

InputStream is = getClass().getResourceAsStream("...

The API doc for getResourceAsStream says

Returns: A InputStream object or null if no resource with this name is found

so the problem is in your resource name. Check the API doc for details of how to code a correct resource name.

ps: What happened to Soduko?

I really should check the API first... Oops. I will check out the API to check how to code the path.

About Sudoku, I think I'm gunna put that on the side, too complicated to do in a console project. That means that I'll take time to code it in the future as a challenge after I get more experienced with GUI and Layouts, as of now I have little experience with GUIs and really get confused with them so I'm thrown off about learning them, especially because there is like 7 or 8 different ones.

Just a couple of observations:

if you do a project like Sudoku right then the hard stuff is all in a "model" that has no UI. YOu can then stick a trivial console UI in front of it, or a full GUI. If you are mixing the UI with the game's logic then your design is almost certainly a problem.
Getting to the "checker"stage isn't hard, although a "solver" is a whole different level of pain.
Finally, when starting out you are going to learn 10% by starting a project (researching new libraries etc), and 90% by finishing it (what really works in practice; 101 useful techniques; 101 subtle traps etc).

There may be a few GUI libraries, but in the end if yur coding for Android that's one solution, if you're coding for PCs in general its either Swing or, if you want to gamble on which way things are going, JavaFX. If you want to get ajob it will almost certainly be with Swing for GUIs (unless you are lucky enough to get straigth into IBM whre they use SWT). Short answer: IMHO, focus on Swing; you can pick up the others later if necessary.

Alright so from what I have found after a while of searching, after no luck with what the structure should look like in the parameter, is that you don't pass the resources folder into the parameter.

So in my case, the parameter passed should be ("Servers.txt"); ?

Not sure if this will help but a while ago I tried reading a file this is what worked for me

try (BufferedReader br = new BufferedReader(new FileReader("C:\\file.txt"))){
            String sCurrentLine;
            while ((sCurrentLine = br.readLine()) != null) {
                System.out.println(sCurrentLine);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } 
    }

I guess you can try using the path like that

Hey Slavi, thanks for posting, however I'm trying to use relative paths and I have to load an input stream. So the way I'm doing it works best, but the problem is getting it to work.

Unless you are doing something clever wuth class loaders, the string "/xxx/yyy.txt" refers to a file xxx.txt in a directory xxx which in turn is in the classpath. What may be confusing is that resources are loaded as if by the classloader, so it uses classpath rather than the default user dir like a file reference.
So if your classes are in a package ppp then somewhere in the classpath you should find

ppp (which is a directory)
ppp/class1.class
ppp/class2.class
resource (which is a directory)
resource/servers.txt

JC, honestly I'm a little confused reading that.

I don't really know how to make that clearer - can someone else try please?

Will it work if I pass what I mentioned earlier?

Yes, provided your directory structure is correct and in the classpath.

Think I got something happening, although I am really not sure. I created a Sound class, shown below, thats used to record audio.

The program is not doing anything however.

TeamVoIP.java

public class TeamVoIP extends JFrame implements Runnable, KeyListener
{
    private static final long   serialVersionUID    = 1L;
    private static final int    WIDTH               = 400;
    private static final int    HEIGHT              = 700;

    private Server              server;
    private Sound               sound;
    private ServerOptions       sOptions;

    public static void main(String[] args)
    {
        // Create a new instance of TeamVoIP and set the Window to be visible
        new TeamVoIP().setVisible(true);
    }

    private TeamVoIP() {
        server = new Server();
        sound = new Sound();
        sOptions = new ServerOptions();
        initGUI();
    }

    public void run()
    {
        if(sound.isRecording() == true)
        {
            //Record sound (possible loop?)
            sound.record();
        }
        try
        {
            //Wait to check again
            Thread.sleep(500);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    private void initGUI()
    {
        // Set up the window
        this.setTitle("TeamVoIP");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(WIDTH, HEIGHT);
        this.setPreferredSize(getSize());
        this.setLocationRelativeTo(null);
        this.setResizable(false);

        // MenuBar
        JMenuBar mBar = new JMenuBar();

        // Create Menus and MenuItems
        JMenu file = new JMenu("File");
        JMenuItem connect = new JMenuItem("Connect");
        JMenuItem disconnect = new JMenuItem("Disconnect");
        JMenuItem host = new JMenuItem("Host...");
        JMenuItem exit = new JMenuItem("Exit");
        JMenu edit = new JMenu("Edit");
        JMenuItem serverOptions = new JMenuItem("Server Options");
        JMenuItem hostOptions = new JMenuItem("Host Options");
        JMenuItem preferences = new JMenuItem("Preferences");
        JMenu help = new JMenu("Help");
        JMenuItem about = new JMenuItem("About");

        // Show Server Options
        serverOptions.addActionListener(new ActionListener()
        {

            @Override
            public void actionPerformed(ActionEvent e)
            {
                //Open server options
                sOptions.setVisible(true);
            }
        });

        // Exit Program if Exit MenuItem was clicked
        exit.addActionListener(new ActionListener()
        {

            @Override
            public void actionPerformed(ActionEvent e)
            {
                //Safely disconnect everything
                server.disconnect();
                exit();
            }

        });

        // Add MenuItems
        file.add(connect);
        file.add(disconnect);
        file.addSeparator();
        file.add(host);
        file.addSeparator();
        file.add(exit);

        edit.add(serverOptions);
        edit.add(hostOptions);
        edit.add(preferences);

        help.add(about);

        // Add Menus to MenuBar
        mBar.add(file);
        mBar.add(edit);
        mBar.add(help);

        this.setJMenuBar(mBar);
        this.addKeyListener(this);
        this.pack();
    }

    // Called when the program is requested to exit
    private void exit()
    {
        this.dispose();
    }

    public void keyTyped(KeyEvent e)
    {}

    public void keyPressed(KeyEvent e)
    {
        //If key is Tilde
        if ((e.getKeyCode()) == 192)
        {
            sound.setRecording(true);
        }
    }

    public void keyReleased(KeyEvent e)
    {
        //If key is Tilde
        if ((e.getKeyCode()) == 192)
        {
            sound.setRecording(false);
        }
    }

}

Sound Class (I assume, where it hangs)

public class Sound
{
    private float                   sampleRate          = 16000; //Should I use 8000?
    private int                     sampleRateInBits    = 16;
    private int                     channels            = 1;
    private boolean                 signed              = true;
    private boolean                 bigEdian            = true;

    private boolean                 recording           = false;

    private AudioFormat             format;
    private DataLine.Info           info;
    private TargetDataLine          line;

    private ByteArrayOutputStream   out;

    public Sound() {
        initAudio();
    }

    private void initAudio()
    {
        //Create a new format
        format = new AudioFormat(sampleRate, sampleRateInBits, channels,
                signed, bigEdian);
        //Get the DataLine info
        info = new DataLine.Info(TargetDataLine.class, format);
        try
        {
            //Prepare to record
            line = (TargetDataLine) AudioSystem.getLine(info);
            line.open(format);
            line.start();
        }
        catch (LineUnavailableException e)
        {
            e.printStackTrace();
        }
    }

    public void record()
    {
        //Create a byte array to store the input
        int bufferSize = (int) format.getSampleRate() * format.getFrameSize();
        byte buffer[] = new byte[bufferSize];
        //Create an output stream, could send this over network, or to a file
        out = new ByteArrayOutputStream();
        while (recording)
        {
            recording = true;
            //Start recording
            System.out.println("recording");
            int count = line.read(buffer, 0, buffer.length);
            if (count > 0)
            {
                out.write(buffer, 0, count);
            }
        }

        try
        {
            //TEMPORARY: Output to file (currently not created)
            OutputStream outputStream = new FileOutputStream(
                    "C:/Users/GeoDoX/Documents/TeamVoIP/audio.mp3");
            out.writeTo(outputStream);
            //Close the stream
            out.close();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    public boolean isRecording()
    {
        return recording;
    }

    public void setRecording(boolean recording)
    {
        this.recording = recording;
    }

}

In case anyone was wondering where I was having the problem with the InputStream, here's the class.

public class ServerOptions extends JFrame
{
    private static final long   serialVersionUID    = 1L;
    private static final int    WIDTH               = 350;
    private static final int    HEIGHT              = 250;

    // GUI Components
    private JComboBox<String>    servers;
    private JButton             newServerBtn;
    private JButton             saveServerBtn;
    private JButton             delServerBtn;
    private JLabel              hostnameLbl;
    private JTextField          hostnameTxt;
    private JLabel              portLbl;
    private JTextField          portTxt;
    private JLabel              passwordLbl;
    private JTextField          passwordTxt;
    private JButton             connectBtn;

    // ComboBox List
    private List<String>     serverList;

    public ServerOptions() {
        initGUI();
    }

    private void initGUI()
    {
        this.setTitle("Server Options");
        this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        this.setSize(WIDTH, HEIGHT);
        this.setPreferredSize(getSize());
        this.setLocationRelativeTo(null);
        this.setResizable(false);

        this.setLayout(null);

        // Setup GUI
        servers = new JComboBox<String>(populateServers());
        newServerBtn = new JButton("New");
        saveServerBtn = new JButton("Save");
        delServerBtn = new JButton("Remove");
        hostnameLbl = new JLabel("Hostname:");
        hostnameTxt = new JTextField();
        portLbl = new JLabel("Port:");
        portTxt = new JTextField();
        passwordLbl = new JLabel("Password:");
        passwordTxt = new JTextField();
        connectBtn = new JButton("Connect");

        servers.setSize(150, 25);
        newServerBtn.setSize(80, 25);
        saveServerBtn.setSize(80, 25);
        delServerBtn.setSize(80, 25);
        hostnameLbl.setSize(200, 25);
        hostnameTxt.setSize(100, 25);
        portLbl.setSize(100, 25);
        portTxt.setSize(100, 25);
        passwordLbl.setSize(100, 25);
        passwordTxt.setSize(100, 25);
        connectBtn.setSize(100, 25);

        hostnameLbl.setFont(hostnameLbl.getFont().deriveFont(18.0f));
        portLbl.setFont(portLbl.getFont().deriveFont(18.0f));
        passwordLbl.setFont(passwordLbl.getFont().deriveFont(18.0f));

        servers.setLocation(WIDTH / 2 - servers.getWidth() / 2,
                HEIGHT / 2 - 110);
        newServerBtn.setLocation(WIDTH / 2 - newServerBtn.getWidth() / 2 - 110,
                HEIGHT / 2 - newServerBtn.getHeight() / 2 - 60);
        saveServerBtn.setLocation(WIDTH / 2 - saveServerBtn.getWidth() / 2,
                HEIGHT / 2 - saveServerBtn.getHeight() / 2 - 60);
        delServerBtn.setLocation(WIDTH / 2 - delServerBtn.getWidth() / 2 + 110,
                HEIGHT / 2 - delServerBtn.getHeight() / 2 - 60);
        hostnameLbl.setLocation(WIDTH / 2 - hostnameLbl.getWidth() / 2 - 5,
                HEIGHT / 2 - 30);
        hostnameTxt.setLocation(WIDTH / 2 + 5, HEIGHT / 2 - 30);
        portLbl.setLocation(WIDTH / 2 - portLbl.getWidth() - 5, HEIGHT / 2 - 5);
        portTxt.setLocation(WIDTH / 2 + 5, HEIGHT / 2 - 5);
        passwordLbl.setLocation(WIDTH / 2 - passwordLbl.getWidth() - 5,
                HEIGHT / 2 + 20);
        passwordTxt.setLocation(WIDTH / 2 + 5, HEIGHT / 2 + 20);
        connectBtn.setLocation(WIDTH / 2 - connectBtn.getWidth() / 2,
                HEIGHT / 2 + 55);

        this.add(servers);
        this.add(newServerBtn);
        this.add(saveServerBtn);
        this.add(delServerBtn);
        this.add(hostnameLbl);
        this.add(hostnameTxt);
        this.add(portLbl);
        this.add(portTxt);
        this.add(passwordLbl);
        this.add(passwordTxt);
        this.add(connectBtn);

        this.pack();
    }

    private String[] populateServers()
    {
        InputStream is = getClass().getResourceAsStream("/Servers.txt");
        InputStreamReader reader = new InputStreamReader(is);
        serverList = new ArrayList<String>();

        try
        {
            BufferedReader fileReader = new BufferedReader(reader);
            String tempStr = "";

            while ((tempStr = fileReader.readLine()) != null)
            {
                if (tempStr.contains("ServerName"))
                {
                    serverList.add(tempStr.substring(13));
                }
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

        return serverList.toArray(new String[serverList.size()]);
    }

}

So the program just does nothing. No output where there should be from the record method, MenuBar still responds, even inserted BreakPoints... Just nothing. Any ideas?

Don't try to test too much code in one go. Strip that right back to the minimum needed to spool sound to a file and get that working first.

This is actually the bare essentials in the Sound class for reading audio into a file. I don't believe the code is even being touched.

I was just showing an updated main class and added the sound class.

PS: Could you check the comments? There's a few questions I have about some of the code in there that I didn't mention.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.