Tutorial: How to use JOgg and JOrbis to play an Ogg Vorbis stream over HTTP

Written by Jon Kristensen1
Version 1.0
Licensed under Creative Commons Attribution-ShareAlike 3.0

Introduction

A couple of days ago, I was looking for a way to include Ogg Vorbis media2 in my Java program, a media player applet. To my happy surprise, I found that JCraft3 had made two free software (LGPL4) libraries, JOgg and JOrbis5, that enabled me to do just that. While these libraries were very powerful, they did not come with as much documentation6 as I would have wanted. It was at this point I realized that writing a tutorial could be my way of contributing to these wonderful projects, so that's what I did.

This tutorial is basically a guide to the sample program that I have written, ExamplePlayer.java. From here, you can either start reading the source code directly, or, you could read the following sections with snippets of the source code and some discussion around it. Note that the discussions are quite breif, and that's only because I'm so busy. If you want to add something to material, please contact me.

I will start by briefly mentioning the different components that ships with JOgg and JOrbis and demonstrate the basic flow of the program. After that, I will guide you through downlading the stream, how to initialize JOgg and JOrbis, how to read the header, how to initialize the sound system, how to read the body of the stream, how to decode the stream and what to clean-up. At the end of this tutorial you will find the source code for the working example application.

Except for the ability to write, compile and execute Java programs, you need to know basic thread management and have an Ogg Vorbis file on a web server. If you feel that you need to improve in any of those fields, everything you need to know about is just a web search query away. Very well, off we go!

What are we dealing with here?

It should be said that there are a number of useful resources about Ogg/Vorbis (basic and advanced) available on their respective web sites7. Anyway, let us analyze the packages and classes that are included with the libraries. Starting with the JOgg package (accessed through com.jcraft.jogg) there are four classes that you will work with directly:

In addition, there is a class Buffer which from what I can tell is only used internally by JOrbis.

The JOrbis package is a bit more extensive. I will leave out some classes that I don't feel are necessary in order to do a basic Ogg Vorbis media player-like implementation. Here's the list:

Take some time and try to look at some of the files in these directories. For the more advanced reader there are also two examples you can look at: DecodeExample and ChainingExample.

The flow of the program

The flow of the program is quite simple and is probably best demonstrated by the run() method:

    /**
     * This method is probably easiest understood by looking at the body.
     * However, it will - if no problems occur - call methods to initialize the
     * JOgg JOrbis libraries, read the header, initialize the sound system, read
     * the body of the stream and clean up.
     */
    public void run()
    {
        // Check that we got an InputStream.
        if(inputStream == null)
        {
            System.err.println("We don't have an input stream and therefor "
                "cannot continue.");
            return;
        }

        // Initialize JOrbis.
        initializeJOrbis();

        /*
         * If we can read the header, we try to inialize the sound system. If we
         * could initialize the sound system, we try to read the body.
         */
        if(readHeader())
        {
            if(initializeSound())
            {
                readBody();
            }
        }

        // Afterwards, we clean up.
        cleanUp();
    }

Note that this method is basically going through the steps mentioned in the introduction. Don't worry if you don't understand what the InputStream object is – we will discuss that in the next section.

Downloading the media

Obviously, we need to get the data before we can start working with it. For this purpose we will work with an InputStream object. This object is basically a link to a data stream that you can read from. Having the URL you want to open as a string, you can use the following two methods to open a connection and get the InputStream object:

    /**
     * Given a string, <code>getUrl()</code> will return an URL object.
     
     @param pUrl the URL to be opened
     @return the URL object
     */
    public URL getUrl(String pUrl)
    {
        URL url = null;

        try
        {
            url = new URL(pUrl);
        }
        catch(MalformedURLException exception)
        {
            System.err.println("Malformed \"url\" parameter: \"" + pUrl + "\"");
        }

        return url;
    }

    /**
     * Sets the <code>inputStream</code> object by taking an URL, opens a
     * connection to it and get the <code>InputStream</code>.
     
     @param pUrl the url to the media file
     */
    private void configureInputStream(URL pUrl)
    {
        // Try to open a connection to the URL.
        try
        {
            urlConnection = pUrl.openConnection();
        }
        catch(UnknownServiceException exception)
        {
            System.err.println("The protocol does not support input.");
        }
        catch(IOException exception)
        {
            System.err.println("An I/O error occoured while trying create the "
                "URL connection.");
        }

        // If we have a connection, try to create an input stream.
        if(urlConnection != null)
        {
            try
            {
                inputStream = urlConnection.getInputStream();
            }
            catch(IOException exception)
            {
                System.err
                    .println("An I/O error occoured while trying to get an "
                        "input stream from the URL.");
                System.err.println(exception);
            }
        }
    }

Now you can easily read bytes from the file by using any of the inputStream.read() methods. Now comes the tricky part, though: interpreting the data.

Taking the first steps: initializing JOgg/JOrbis and creating the run() method

Before we can go any further, there is some information about the media file that is necessary at this point. In order to be able to work with this data we will need a couple of JOgg/JOrbis tools. Let's start by globally defining a couple of objects:

    // Here are the four required JOgg objects...
    private Packet joggPacket = new Packet();
    private Page joggPage = new Page();
    private StreamState joggStreamState = new StreamState();
    private SyncState joggSyncState = new SyncState();

    // ... followed by the four required JOrbis objects.
    private DspState jorbisDspState = new DspState();
    private Block jorbisBlock = new Block(jorbisDspState);
    private Comment jorbisComment = new Comment();
    private Info jorbisInfo = new Info();

Now we need to initialize JOrbis, which is done by initializing the SyncState object, prepare SyncState's internal buffer (clear out previously occupied space, etc) and setting our current buffer to the data this buffer:

    /**
     * Initializes JOrbis. First, we initialize the <code>SyncState</code>
     * object. After that, we prepare the <code>SyncState</code> buffer. Then
     * we "initialize" our buffer, taking the data in <code>SyncState</code>.
     */
    private void initializeJOrbis()
    {
        debugOutput("Initializing JOrbis.");

        // Initialize SyncState
        joggSyncState.init();

        // Prepare the to SyncState internal buffer
        joggSyncState.buffer(bufferSize);

        /*
         * Fill the buffer with the data from SyncState's internal buffer. Note
         * how the size of this new buffer is different from bufferSize.
         */
        buffer = joggSyncState.data;

        debugOutput("Done initializing JOrbis.");
    }

Now we can move forward to reading the header (the first three pages of the stream).

Reading the header

Before we start reading the header9, we need to define a couple of variables to work with the InputStream:

    /*
     * We need a buffer, it's size, a count to know how many bytes we have read
     * and an index to keep track of where we are. This is standard networking
     * stuff used with read().
     */
    byte[] buffer = null;
    int bufferSize = 2048;
    int count = 0;
    int index = 0;

Now, this part is a bit tricky. We basically need to read the first three packets of the stream and set up some things. Here's a list of the specific things we will need to do:

  1. Read from the InputStream

  2. Tell SyncState how many bytes we read (we always need to do this)

  3. Ask SyncState to generate a Page (we need to get more data if we need it and need to break if there's a hole in the data)

  4. Initialize and reset the StreamState object

  5. Initialize the Info and Comment objects

  6. Check the Page and the Packet for various errors

  7. Give the first packet to the JOrbis Info and Comment classes

  8. Basically redo step 3 twice and extracting the packet for the remaining two packets, giving them to the Info and Comment classes

These eight items and some other details are being taken cared of by the following code:

    /**
     * This method reads the header of the stream, which consists of three
     * packets.
     
     @return true if the header was successfully read, false otherwise
     */
    private boolean readHeader()
    {
        debugOutput("Starting to read the header.");

        /*
         * Variable used in loops below. While we need more data, we will
         * continue to read from the InputStream.
         */
        boolean needMoreData = true;

        /*
         * We will read the first three packets of the header. We start off by
         * defining packet = 1 and increment that value whenever we have
         * successfully read another packet.
         */
        int packet = 1;

        /*
         * While we need more data (which we do until we have read the three
         * header packets), this loop reads from the stream and has a big
         * <code>switch</code> statement which does what it's supposed to do in
         * regards to the current packet.
         */
        while(needMoreData)
        {
            // Read from the InputStream.
            try
            {
                count = inputStream.read(buffer, index, bufferSize);
            }
            catch(IOException exception)
            {
                System.err.println("Could not read from the input stream.");
                System.err.println(exception);
            }

            // We let SyncState know how many bytes we read.
            joggSyncState.wrote(count);

            /*
             * We want to read the first three packets. For the first packet, we
             * need to initialize the StreamState object and a couple of other
             * things. For packet two and three, the procedure is the same: we
             * take out a page, and then we take out the packet.
             */
            switch(packet)
            {
                // The first packet.
                case 1:
                {
                    // We take out a page.
                    switch(joggSyncState.pageout(joggPage))
                    {
                        // If there is a hole in the data, we must exit.
                        case -1:
                        {
                            System.err.println("There is a hole in the first "
                                "packet data.");
                            return false;
                        }

                        // If we need more data, we break to get it.
                        case 0:
                        {
                            break;
                        }

                        /*
                         * We got where we wanted. We have successfully read the
                         * first packet, and we will now initialize and reset
                         * StreamState, and initialize the Info and Comment
                         * objects. Afterwards we will check that the page
                         * doesn't contain any errors, that the packet doesn't
                         * contain any errors and that it's Vorbis data.
                         */
                        case 1:
                        {
                            // Initializes and resets StreamState.
                            joggStreamState.init(joggPage.serialno());
                            joggStreamState.reset();

                            // Initializes the Info and Comment objects.
                            jorbisInfo.init();
                            jorbisComment.init();

                            // Check the page (serial number and stuff).
                            if(joggStreamState.pagein(joggPage== -1)
                            {
                                System.err.println("We got an error while "
                                    "reading the first header page.");
                                return false;
                            }

                            /*
                             * Try to extract a packet. All other return values
                             * than "1" indicates there's something wrong.
                             */
                            if(joggStreamState.packetout(joggPacket!= 1)
                            {
                                System.err.println("We got an error while "
                                    "reading the first header packet.");
                                return false;
                            }

                            /*
                             * We give the packet to the Info object, so that it
                             * can extract the Comment-related information,
                             * among other things. If this fails, it's not
                             * Vorbis data.
                             */
                            if(jorbisInfo.synthesis_headerin(jorbisComment,
                                joggPacket0)
                            {
                                System.err.println("We got an error while "
                                    "interpreting the first packet. "
                                    "Apparantly, it's not Vorbis data.");
                                return false;
                            }

                            // We're done here, let's increment "packet".
                            packet++;
                            break;
                        }
                    }

                    /*
                     * Note how we are NOT breaking here if we have proceeded to
                     * the second packet. We don't want to read from the input
                     * stream again if it's not necessary.
                     */
                    if(packet == 1break;
                }

                // The code for the second and third packets follow.
                case 2:    case 3:
                {
                    // Try to get a new page again.
                    switch(joggSyncState.pageout(joggPage))
                    {
                        // If there is a hole in the data, we must exit.
                        case -1:
                        {
                            System.err.println("There is a hole in the second "
                                "or third packet data.");
                            return false;
                        }

                        // If we need more data, we break to get it.
                        case 0:
                        {
                            break;
                        }

                        /*
                         * Here is where we take the page, extract a packet and
                         * and (if everything goes well) give the information to
                         * the Info and Comment objects like we did above.
                         */
                        case 1:
                        {
                            // Share the page with the StreamState object.
                            joggStreamState.pagein(joggPage);

                            /*
                             * Just like the switch(...packetout...) lines
                             * above.
                             */
                            switch(joggStreamState.packetout(joggPacket))
                            {
                                // If there is a hole in the data, we must exit.
                                case -1:
                                {
                                    System.err
                                        .println("There is a hole in the first"
                                            "packet data.");
                                    return false;
                                }

                                // If we need more data, we break to get it.
                                case 0:
                                {
                                    break;
                                }

                                // We got a packet, let's process it.
                                case 1:
                                {
                                    /*
                                     * Like above, we give the packet to the
                                     * Info and Comment objects.
                                     */
                                    jorbisInfo.synthesis_headerin(
                                        jorbisComment, joggPacket);

                                    // Increment packet.
                                    packet++;

                                    if(packet == 4)
                                    {
                                        /*
                                         * There is no fourth packet, so we will
                                         * just end the loop here.
                                         */
                                        needMoreData = false;
                                    }

                                    break;
                                }
                            }

                            break;
                        }
                    }

                    break;
                }
            }

            // We get the new index and an updated buffer.
            index = joggSyncState.buffer(bufferSize);
            buffer = joggSyncState.data;

            /*
             * If we need more data but can't get it, the stream doesn't contain
             * enough information.
             */
            if(count == && needMoreData)
            {
                System.err.println("Not enough header data was supplied.");
                return false;
            }
        }

        debugOutput("Finished reading the header.");

        return true;
    }

That was that.

Initialize sound

We now have the data that we need to initialize the sound system (bitrate, etc). First, let us declare a couple of variables necessary for this:

    // The source data line onto which data can be written.
    private SourceDataLine outputLine = null;

    // A three-dimensional an array with PCM information. 
    private float[][][] pcmInfo;

    // The index for the PCM information.
    private int[] pcmIndex;

This procedure is done through the following steps:

  1. Initialize the DspInfo object, and let it set up the Block object

  2. Open a line to the source data line, which is a data line onto which audio data may be written

  3. Define the PCM data variables

Please see the following method:

    /**
     * This method starts the sound system. It starts with initializing the
     <code>DspState</code> object, after which it sets up the
     <code>Block</code> object. Last but not least, it opens a line to the
     * source data line.
     
     @return true if the sound system was successfully started, false
     *         otherwise
     */
    private boolean initializeSound()
    {
        debugOutput("Initializing the sound system.");

        // This buffer is used by the decoding method.
        convertedBufferSize = bufferSize * 2;
        convertedBuffer = new byte[convertedBufferSize];

        // Initializes the DSP synthesis.
        jorbisDspState.synthesis_init(jorbisInfo);

        // Make the Block object aware of the DSP.
        jorbisBlock.init(jorbisDspState);

        // Wee need to know the channels and rate.
        int channels = jorbisInfo.channels;
        int rate = jorbisInfo.rate;

        // Creates an AudioFormat object and a DataLine.Info object.
        AudioFormat audioFormat = new AudioFormat((floatrate, 16, channels,
            true, false);
        DataLine.Info datalineInfo = new DataLine.Info(SourceDataLine.class,
            audioFormat, AudioSystem.NOT_SPECIFIED);

        // Check if the line is supported.
        if(!AudioSystem.isLineSupported(datalineInfo))
        {
            System.err.println("Audio output line is not supported.");
            return false;
        }

        /*
         * Everything seems to be alright. Let's try to open a line with the
         * specified format and start the source data line.
         */
        try
        {
            outputLine = (SourceDataLineAudioSystem.getLine(datalineInfo);
            outputLine.open(audioFormat);
        }
        catch(LineUnavailableException exception)
        {
            System.out.println("The audio output line could not be opened due "
                "to resource restrictions.");
            System.err.println(exception);
            return false;
        }
        catch(IllegalStateException exception)
        {
            System.out.println("The audio output line is already open.");
            System.err.println(exception);
            return false;
        }
        catch(SecurityException exception)
        {
            System.out.println("The audio output line could not be opened due "
                "to security restrictions.");
            System.err.println(exception);
            return false;
        }

        // Start it.
        outputLine.start();

        /*
         * We create the PCM variables. The index is an array with the same
         * length as the number of audio channels.
         */
        pcmInfo = new float[1][][];
        pcmIndex = new int[jorbisInfo.channels];

        debugOutput("Done initializing the sound system.");

        return true;
    }

We are now ready to start reading the body of the stream.

Read body and play the file

This method works like readHeader(), it tries to read a page and whenever it succeeds it tries to extract the packets (which may be plenty). Whenever it has extracted a Packet, it will call the helper method decodeCurrentPacket(). Here it is:

    /**
     * This method reads the entire stream body. Whenever it extracts a packet,
     * it will decode it by calling <code>decodeCurrentPacket()</code>.
     */
    private void readBody()
    {
        debugOutput("Reading the body.");

        /*
         * Variable used in loops below, like in readHeader(). While we need
         * more data, we will continue to read from the InputStream.
         */
        boolean needMoreData = true;

        while(needMoreData)
        {
            switch(joggSyncState.pageout(joggPage))
            {
                // If there is a hole in the data, we just proceed.
                case -1:
                {
                    debugOutput("There is a hole in the data. We proceed.");
                }

                // If we need more data, we break to get it.
                case 0:
                {
                    break;
                }

                // If we have successfully checked out a page, we continue.
                case 1:
                {
                    // Give the page to the StreamState object.
                    joggStreamState.pagein(joggPage);

                    // If granulepos() returns "0", we don't need more data.
                    if(joggPage.granulepos() == 0)
                    {
                        needMoreData = false;
                        break;
                    }

                    // Here is where we process the packets.
                    processPackets: while(true)
                    {
                        switch(joggStreamState.packetout(joggPacket))
                        {
                            // Is it a hole in the data?
                            case -1:
                            {
                                debugOutput("There is a hole in the data, we "
                                    "continue though.");
                            }

                            // If we need more data, we break to get it.
                            case 0:
                            {
                                break processPackets;
                            }

                            /*
                             * If we have the data we need, we decode the
                             * packet.
                             */
                            case 1:
                            {
                                decodeCurrentPacket();
                            }
                        }
                    }

                    /*
                     * If the page is the end-of-stream, we don't need more
                     * data.
                     */
                    if(joggPage.eos() != 0needMoreData = false;
                }
            }

            // If we need more data
            if(needMoreData)
            {
                // We get the new index and an updated buffer.
                index = joggSyncState.buffer(bufferSize);
                buffer = joggSyncState.data;

                // Read from the InputStream.
                try
                {
                    count = inputStream.read(buffer, index, bufferSize);
                }
                catch(Exception e)
                {
                    System.err.println(e);
                    return;
                }

                // We let SyncState know how many bytes we read.
                joggSyncState.wrote(count);

                // There's no more data in the stream.
                if(count == 0needMoreData = false;
            }
        }
        debugOutput("Done reading the body.");
    }

This method is pretty straight-forward. However, the decodeCurrentPacket() is a bit trickier. This is what it does:

  1. Check that the packet is an audio data packet, and if so, give the packet to the Block object

  2. Get the PCM information, and while we have samples to process, repeat 3-9

  3. For every channel, repeat 4-9

  4. For every sample in the range, repeat 5-9

  5. Get the value pcmInfo[0][<THE CHANNEL>][<THE SAMPLE NUMBER>] * 32767

  6. Make sure the value does not exceed or fall below +-32767

  7. XOR the value with 32768 (decimal) or 1000000000000000 (binary)

  8. Split our value into two, one with the last byte and one with the byte next to the left (that is, (byte) value and (byte) (value >>> 8))

  9. Move the sample index forward by two times the number of channels

  10. Write the buffer to the audio output line

  11. Update the DspState object with how many samples we have processed

Below is an example on how decodeCurrentPacket() may be implemented:

    /**
     *  Decodes the current packet and sends it to the audio output line.
     */
    private void decodeCurrentPacket()
    {
        int samples;

        // Check that the packet is a audio data packet etc.
        if(jorbisBlock.synthesis(joggPacket== 0)
        {
            // Give the block to the DspState object.
            jorbisDspState.synthesis_blockin(jorbisBlock);
        }

        // We need to know how many samples to process.
        int range;

        /*
         * Get the PCM information and count the samples. And while these
         * samples are more than zero...
         */
        while((samples = jorbisDspState.synthesis_pcmout(pcmInfo, pcmIndex))
            0)
        {
            // We need to know for how many samples we are going to process.
            if(samples < convertedBufferSize)
            {
                range = samples;
            }
            else
            {
                range = convertedBufferSize;
            }

            // For each channel...
            for(int i = 0; i < jorbisInfo.channels; i++)
            {
                int sampleIndex = i * 2;

                // For every sample in our range...
                for(int j = 0; j < range; j++)
                {
                    /*
                     * Get the PCM value for the channel at the correct
                     * position.
                     */
                    int value = (int) (pcmInfo[0][i][pcmIndex[i+ j32767);

                    /*
                     * We make sure our value doesn't exceed or falls below
                     * +-32767.
                     */
                    if(value > 32767)
                    {
                        value = 32767;
                    }
                    if(value < -32768)
                    {
                        value = -32768;
                    }

                    /*
                     * It the value is less than zero, we bitwise-or it with
                     * 32768 (which is 1000000000000000 = 10^15).
                     */
                    if(value < 0value = value | 32768;

                    /*
                     * Take our value and split it into two, one with the last
                     * byte and one with the first byte.
                     */
                    convertedBuffer[sampleIndex(byte) (value);
                    convertedBuffer[sampleIndex + 1(byte) (value >>> 8);

                    /*
                     * Move the sample index forward by two (since that's how
                     * many values we get at once) times the number of channels.
                     */
                    sampleIndex += (jorbisInfo.channels);
                }
            }

            // Write the buffer to the audio output line.
            outputLine.write(convertedBuffer, 0* jorbisInfo.channels
                * range);

            // Update the DspState object.
            jorbisDspState.synthesis_read(range);
        }
    }

Moving on!

Cleaning up

When we have successfully decoded the whole stream we want to do some clean-up. The following method takes care of that and pretty much speaks for itself:

    /**
     * A clean-up method, called when everything is finished. Clears the
     * JOgg/JOrbis objects and closes the <code>InputStream</code>.
     */
    private void cleanUp()
    {
        debugOutput("Cleaning up.");

        // Clear the necessary JOgg/JOrbis objects.
        joggStreamState.clear();
        jorbisBlock.clear();
        jorbisDspState.clear();
        jorbisInfo.clear();
        joggSyncState.clear();

        // Closes the stream.
        try
        {
            if(inputStream != nullinputStream.close();
        }
        catch(Exception e)
        {
        }

        debugOutput("Done cleaning up.");
    }

That's it!

The complete program

Below is the complete program. If you want a Java file, try fetching ExamplePlayer.java.

/*
 * Copyright © Jon Kristensen, 2008.
 * All rights reserved.
 
 * This is version 1.0 of this source code, made to work with JOrbis 1.x. The
 * last time this file was updated was the 15th of March, 2008.
 
 * Version history:
 
 * 1.0: Initial release.
 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 
 *   * Neither the name of jonkri.com nor the names of its contributors may be
 *     used to endorse or promote products derived from this software without
 *     specific prior written permission.
 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

import com.jcraft.jogg.*;
import com.jcraft.jorbis.*;
import java.io.InputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownServiceException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

/**
 * The <code>ExamplePlayer</code> thread class will simply download and play
 * OGG media. All you need to do is supply a valid URL as the first argument.
 
 @author Jon Kristensen
 @version 1.0
 */
public class ExamplePlayer extends Thread
{
    // If you wish to debug this source, please set the variable below to true.
    private final boolean debugMode = true;

    /*
     * URLConnection and InputStream objects so that we can open a connection to
     * the media file.
     */
    private URLConnection urlConnection = null;
    private InputStream inputStream = null;

    /*
     * We need a buffer, it's size, a count to know how many bytes we have read
     * and an index to keep track of where we are. This is standard networking
     * stuff used with read().
     */
    byte[] buffer = null;
    int bufferSize = 2048;
    int count = 0;
    int index = 0;

    /*
     * JOgg and JOrbis require fields for the converted buffer. This is a buffer
     * that is modified in regards to the number of audio channels. Naturally,
     * it will also need a size.
     */
    byte[] convertedBuffer;
    int convertedBufferSize;

    // The source data line onto which data can be written.
    private SourceDataLine outputLine = null;

    // A three-dimensional an array with PCM information. 
    private float[][][] pcmInfo;

    // The index for the PCM information.
    private int[] pcmIndex;

    // Here are the four required JOgg objects...
    private Packet joggPacket = new Packet();
    private Page joggPage = new Page();
    private StreamState joggStreamState = new StreamState();
    private SyncState joggSyncState = new SyncState();

    // ... followed by the four required JOrbis objects.
    private DspState jorbisDspState = new DspState();
    private Block jorbisBlock = new Block(jorbisDspState);
    private Comment jorbisComment = new Comment();
    private Info jorbisInfo = new Info();

    /**
     * The programs <code>main()</code> method. Will read the first
     * command-line argument and use it as URL, after which it will start the
     * thread.
     
     @param args command-line arguments
     */
    public static void main(String[] args)
    {
        // Set the URL as the first argument, if any.
        String url = args.length > ? url = args[0] : null;

        /*
         * If the url variable is set, start the thread. If not, give an error
         * and die.
         */
        if(url != null)
        {
            ExamplePlayer examplePlayer = new ExamplePlayer(url);
            examplePlayer.start();
        }
        else
        {
            System.err.println("Please provide an argument with the file to "
                "play.");
        }
    }

    /**
     * The constructor; will configure the <code>InputStream</code>.
     
     @param pUrl the URL to be opened
     */
    ExamplePlayer(String pUrl)
    {
        configureInputStream(getUrl(pUrl));
    }

    /**
     * Given a string, <code>getUrl()</code> will return an URL object.
     
     @param pUrl the URL to be opened
     @return the URL object
     */
    public URL getUrl(String pUrl)
    {
        URL url = null;

        try
        {
            url = new URL(pUrl);
        }
        catch(MalformedURLException exception)
        {
            System.err.println("Malformed \"url\" parameter: \"" + pUrl + "\"");
        }

        return url;
    }

    /**
     * Sets the <code>inputStream</code> object by taking an URL, opens a
     * connection to it and get the <code>InputStream</code>.
     
     @param pUrl the url to the media file
     */
    private void configureInputStream(URL pUrl)
    {
        // Try to open a connection to the URL.
        try
        {
            urlConnection = pUrl.openConnection();
        }
        catch(UnknownServiceException exception)
        {
            System.err.println("The protocol does not support input.");
        }
        catch(IOException exception)
        {
            System.err.println("An I/O error occoured while trying create the "
                "URL connection.");
        }

        // If we have a connection, try to create an input stream.
        if(urlConnection != null)
        {
            try
            {
                inputStream = urlConnection.getInputStream();
            }
            catch(IOException exception)
            {
                System.err
                    .println("An I/O error occoured while trying to get an "
                        "input stream from the URL.");
                System.err.println(exception);
            }
        }
    }

    /**
     * This method is probably easiest understood by looking at the body.
     * However, it will - if no problems occur - call methods to initialize the
     * JOgg JOrbis libraries, read the header, initialize the sound system, read
     * the body of the stream and clean up.
     */
    public void run()
    {
        // Check that we got an InputStream.
        if(inputStream == null)
        {
            System.err.println("We don't have an input stream and therefor "
                "cannot continue.");
            return;
        }

        // Initialize JOrbis.
        initializeJOrbis();

        /*
         * If we can read the header, we try to inialize the sound system. If we
         * could initialize the sound system, we try to read the body.
         */
        if(readHeader())
        {
            if(initializeSound())
            {
                readBody();
            }
        }

        // Afterwards, we clean up.
        cleanUp();
    }

    /**
     * Initializes JOrbis. First, we initialize the <code>SyncState</code>
     * object. After that, we prepare the <code>SyncState</code> buffer. Then
     * we "initialize" our buffer, taking the data in <code>SyncState</code>.
     */
    private void initializeJOrbis()
    {
        debugOutput("Initializing JOrbis.");

        // Initialize SyncState
        joggSyncState.init();

        // Prepare the to SyncState internal buffer
        joggSyncState.buffer(bufferSize);

        /*
         * Fill the buffer with the data from SyncState's internal buffer. Note
         * how the size of this new buffer is different from bufferSize.
         */
        buffer = joggSyncState.data;

        debugOutput("Done initializing JOrbis.");
    }

    /**
     * This method reads the header of the stream, which consists of three
     * packets.
     
     @return true if the header was successfully read, false otherwise
     */
    private boolean readHeader()
    {
        debugOutput("Starting to read the header.");

        /*
         * Variable used in loops below. While we need more data, we will
         * continue to read from the InputStream.
         */
        boolean needMoreData = true;

        /*
         * We will read the first three packets of the header. We start off by
         * defining packet = 1 and increment that value whenever we have
         * successfully read another packet.
         */
        int packet = 1;

        /*
         * While we need more data (which we do until we have read the three
         * header packets), this loop reads from the stream and has a big
         * <code>switch</code> statement which does what it's supposed to do in
         * regards to the current packet.
         */
        while(needMoreData)
        {
            // Read from the InputStream.
            try
            {
                count = inputStream.read(buffer, index, bufferSize);
            }
            catch(IOException exception)
            {
                System.err.println("Could not read from the input stream.");
                System.err.println(exception);
            }

            // We let SyncState know how many bytes we read.
            joggSyncState.wrote(count);

            /*
             * We want to read the first three packets. For the first packet, we
             * need to initialize the StreamState object and a couple of other
             * things. For packet two and three, the procedure is the same: we
             * take out a page, and then we take out the packet.
             */
            switch(packet)
            {
                // The first packet.
                case 1:
                {
                    // We take out a page.
                    switch(joggSyncState.pageout(joggPage))
                    {
                        // If there is a hole in the data, we must exit.
                        case -1:
                        {
                            System.err.println("There is a hole in the first "
                                "packet data.");
                            return false;
                        }

                        // If we need more data, we break to get it.
                        case 0:
                        {
                            break;
                        }

                        /*
                         * We got where we wanted. We have successfully read the
                         * first packet, and we will now initialize and reset
                         * StreamState, and initialize the Info and Comment
                         * objects. Afterwards we will check that the page
                         * doesn't contain any errors, that the packet doesn't
                         * contain any errors and that it's Vorbis data.
                         */
                        case 1:
                        {
                            // Initializes and resets StreamState.
                            joggStreamState.init(joggPage.serialno());
                            joggStreamState.reset();

                            // Initializes the Info and Comment objects.
                            jorbisInfo.init();
                            jorbisComment.init();

                            // Check the page (serial number and stuff).
                            if(joggStreamState.pagein(joggPage) == -1)
                            {
                                System.err.println("We got an error while "
                                    "reading the first header page.");
                                return false;
                            }

                            /*
                             * Try to extract a packet. All other return values
                             * than "1" indicates there's something wrong.
                             */
                            if(joggStreamState.packetout(joggPacket) != 1)
                            {
                                System.err.println("We got an error while "
                                    "reading the first header packet.");
                                return false;
                            }

                            /*
                             * We give the packet to the Info object, so that it
                             * can extract the Comment-related information,
                             * among other things. If this fails, it's not
                             * Vorbis data.
                             */
                            if(jorbisInfo.synthesis_headerin(jorbisComment,
                                joggPacket) < 0)
                            {
                                System.err.println("We got an error while "
                                    "interpreting the first packet. "
                                    "Apparantly, it's not Vorbis data.");
                                return false;
                            }

                            // We're done here, let's increment "packet".
                            packet++;
                            break;
                        }
                    }

                    /*
                     * Note how we are NOT breaking here if we have proceeded to
                     * the second packet. We don't want to read from the input
                     * stream again if it's not necessary.
                     */
                    if(packet == 1break;
                }

                // The code for the second and third packets follow.
                case 2:    case 3:
                {
                    // Try to get a new page again.
                    switch(joggSyncState.pageout(joggPage))
                    {
                        // If there is a hole in the data, we must exit.
                        case -1:
                        {
                            System.err.println("There is a hole in the second "
                                "or third packet data.");
                            return false;
                        }

                        // If we need more data, we break to get it.
                        case 0:
                        {
                            break;
                        }

                        /*
                         * Here is where we take the page, extract a packet and
                         * and (if everything goes well) give the information to
                         * the Info and Comment objects like we did above.
                         */
                        case 1:
                        {
                            // Share the page with the StreamState object.
                            joggStreamState.pagein(joggPage);

                            /*
                             * Just like the switch(...packetout...) lines
                             * above.
                             */
                            switch(joggStreamState.packetout(joggPacket))
                            {
                                // If there is a hole in the data, we must exit.
                                case -1:
                                {
                                    System.err
                                        .println("There is a hole in the first"
                                            "packet data.");
                                    return false;
                                }

                                // If we need more data, we break to get it.
                                case 0:
                                {
                                    break;
                                }

                                // We got a packet, let's process it.
                                case 1:
                                {
                                    /*
                                     * Like above, we give the packet to the
                                     * Info and Comment objects.
                                     */
                                    jorbisInfo.synthesis_headerin(
                                        jorbisComment, joggPacket);

                                    // Increment packet.
                                    packet++;

                                    if(packet == 4)
                                    {
                                        /*
                                         * There is no fourth packet, so we will
                                         * just end the loop here.
                                         */
                                        needMoreData = false;
                                    }

                                    break;
                                }
                            }

                            break;
                        }
                    }

                    break;
                }
            }

            // We get the new index and an updated buffer.
            index = joggSyncState.buffer(bufferSize);
            buffer = joggSyncState.data;

            /*
             * If we need more data but can't get it, the stream doesn't contain
             * enough information.
             */
            if(count == && needMoreData)
            {
                System.err.println("Not enough header data was supplied.");
                return false;
            }
        }

        debugOutput("Finished reading the header.");

        return true;
    }

    /**
     * This method starts the sound system. It starts with initializing the
     <code>DspState</code> object, after which it sets up the
     <code>Block</code> object. Last but not least, it opens a line to the
     * source data line.
     
     @return true if the sound system was successfully started, false
     *         otherwise
     */
    private boolean initializeSound()
    {
        debugOutput("Initializing the sound system.");

        // This buffer is used by the decoding method.
        convertedBufferSize = bufferSize * 2;
        convertedBuffer = new byte[convertedBufferSize];

        // Initializes the DSP synthesis.
        jorbisDspState.synthesis_init(jorbisInfo);

        // Make the Block object aware of the DSP.
        jorbisBlock.init(jorbisDspState);

        // Wee need to know the channels and rate.
        int channels = jorbisInfo.channels;
        int rate = jorbisInfo.rate;

        // Creates an AudioFormat object and a DataLine.Info object.
        AudioFormat audioFormat = new AudioFormat((float) rate, 16, channels,
            true, false);
        DataLine.Info datalineInfo = new DataLine.Info(SourceDataLine.class,
            audioFormat, AudioSystem.NOT_SPECIFIED);

        // Check if the line is supported.
        if(!AudioSystem.isLineSupported(datalineInfo))
        {
            System.err.println("Audio output line is not supported.");
            return false;
        }

        /*
         * Everything seems to be alright. Let's try to open a line with the
         * specified format and start the source data line.
         */
        try
        {
            outputLine = (SourceDataLine) AudioSystem.getLine(datalineInfo);
            outputLine.open(audioFormat);
        }
        catch(LineUnavailableException exception)
        {
            System.out.println("The audio output line could not be opened due "
                "to resource restrictions.");
            System.err.println(exception);
            return false;
        }
        catch(IllegalStateException exception)
        {
            System.out.println("The audio output line is already open.");
            System.err.println(exception);
            return false;
        }
        catch(SecurityException exception)
        {
            System.out.println("The audio output line could not be opened due "
                "to security restrictions.");
            System.err.println(exception);
            return false;
        }

        // Start it.
        outputLine.start();

        /*
         * We create the PCM variables. The index is an array with the same
         * length as the number of audio channels.
         */
        pcmInfo = new float[1][][];
        pcmIndex = new int[jorbisInfo.channels];

        debugOutput("Done initializing the sound system.");

        return true;
    }

    /**
     * This method reads the entire stream body. Whenever it extracts a packet,
     * it will decode it by calling <code>decodeCurrentPacket()</code>.
     */
    private void readBody()
    {
        debugOutput("Reading the body.");

        /*
         * Variable used in loops below, like in readHeader(). While we need
         * more data, we will continue to read from the InputStream.
         */
        boolean needMoreData = true;

        while(needMoreData)
        {
            switch(joggSyncState.pageout(joggPage))
            {
                // If there is a hole in the data, we just proceed.
                case -1:
                {
                    debugOutput("There is a hole in the data. We proceed.");
                }

                // If we need more data, we break to get it.
                case 0:
                {
                    break;
                }

                // If we have successfully checked out a page, we continue.
                case 1:
                {
                    // Give the page to the StreamState object.
                    joggStreamState.pagein(joggPage);

                    // If granulepos() returns "0", we don't need more data.
                    if(joggPage.granulepos() == 0)
                    {
                        needMoreData = false;
                        break;
                    }

                    // Here is where we process the packets.
                    processPackets: while(true)
                    {
                        switch(joggStreamState.packetout(joggPacket))
                        {
                            // Is it a hole in the data?
                            case -1:
                            {
                                debugOutput("There is a hole in the data, we "
                                    "continue though.");
                            }

                            // If we need more data, we break to get it.
                            case 0:
                            {
                                break processPackets;
                            }

                            /*
                             * If we have the data we need, we decode the
                             * packet.
                             */
                            case 1:
                            {
                                decodeCurrentPacket();
                            }
                        }
                    }

                    /*
                     * If the page is the end-of-stream, we don't need more
                     * data.
                     */
                    if(joggPage.eos() != 0) needMoreData = false;
                }
            }

            // If we need more data
            if(needMoreData)
            {
                // We get the new index and an updated buffer.
                index = joggSyncState.buffer(bufferSize);
                buffer = joggSyncState.data;

                // Read from the InputStream.
                try
                {
                    count = inputStream.read(buffer, index, bufferSize);
                }
                catch(Exception e)
                {
                    System.err.println(e);
                    return;
                }

                // We let SyncState know how many bytes we read.
                joggSyncState.wrote(count);

                // There's no more data in the stream.
                if(count == 0) needMoreData = false;
            }
        }
        debugOutput("Done reading the body.");
    }

    /**
     * A clean-up method, called when everything is finished. Clears the
     * JOgg/JOrbis objects and closes the <code>InputStream</code>.
     */
    private void cleanUp()
    {
        debugOutput("Cleaning up.");

        // Clear the necessary JOgg/JOrbis objects.
        joggStreamState.clear();
        jorbisBlock.clear();
        jorbisDspState.clear();
        jorbisInfo.clear();
        joggSyncState.clear();

        // Closes the stream.
        try
        {
            if(inputStream != null) inputStream.close();
        }
        catch(Exception e)
        {
        }

        debugOutput("Done cleaning up.");
    }

    /**
     *  Decodes the current packet and sends it to the audio output line.
     */
    private void decodeCurrentPacket()
    {
        int samples;

        // Check that the packet is a audio data packet etc.
        if(jorbisBlock.synthesis(joggPacket) == 0)
        {
            // Give the block to the DspState object.
            jorbisDspState.synthesis_blockin(jorbisBlock);
        }

        // We need to know how many samples to process.
        int range;

        /*
         * Get the PCM information and count the samples. And while these
         * samples are more than zero...
         */
        while((samples = jorbisDspState.synthesis_pcmout(pcmInfo, pcmIndex))
            0)
        {
            // We need to know for how many samples we are going to process.
            if(samples < convertedBufferSize)
            {
                range = samples;
            }
            else
            {
                range = convertedBufferSize;
            }

            // For each channel...
            for(int i = 0; i < jorbisInfo.channels; i++)
            {
                int sampleIndex = i * 2;

                // For every sample in our range...
                for(int j = 0; j < range; j++)
                {
                    /*
                     * Get the PCM value for the channel at the correct
                     * position.
                     */
                    int value = (int) (pcmInfo[0][i][pcmIndex[i] + j] * 32767);

                    /*
                     * We make sure our value doesn't exceed or falls below
                     * +-32767.
                     */
                    if(value > 32767)
                    {
                        value = 32767;
                    }
                    if(value < -32768)
                    {
                        value = -32768;
                    }

                    /*
                     * It the value is less than zero, we bitwise-or it with
                     * 32768 (which is 1000000000000000 = 10^15).
                     */
                    if(value < 0) value = value | 32768;

                    /*
                     * Take our value and split it into two, one with the last
                     * byte and one with the first byte.
                     */
                    convertedBuffer[sampleIndex] = (byte) (value);
                    convertedBuffer[sampleIndex + 1] = (byte) (value >>> 8);

                    /*
                     * Move the sample index forward by two (since that's how
                     * many values we get at once) times the number of channels.
                     */
                    sampleIndex += * (jorbisInfo.channels);
                }
            }

            // Write the buffer to the audio output line.
            outputLine.write(convertedBuffer, 0* jorbisInfo.channels
                * range);

            // Update the DspState object.
            jorbisDspState.synthesis_read(range);
        }
    }

    /**
     * This method is being called internally to output debug information
     * whenever that is wanted.
     
     @param output the debug output information
     */
    private void debugOutput(String output)
    {
        if(debugMode) System.out.println("Debug: " + output);
    }
}

1Jon Kristensen can be reached at info 1 jonkri 2 org (replace “1” with “@” and “2” with “.” and remove the spaces).

2A nice introduction to Ogg and Vorbis can be found at their respective Wikipedia articles: http://en.wikipedia.org/wiki/Ogg and http://en.wikipedia.org/wiki/Vorbis.

3JCraft is a Japanese company that makes a lot of interesting Java-related products. Visit them at http://www.jcraft.com/.

4GPL is GNU Lesser Public License found at http://www.gnu.org/licenses/lgpl.html. Although the license is quite generous you should make sure that you understand the terms of this license before you use JOgg and JOrbis.

5Both JOgg and JOrbis can be found at http://www.jcraft.com/jorbis/.

6In all fairness it should be mentioned that there is a Java example application called JOrbisPlayer which is licensed under GNU GPL. However, this program is not very well commented and is probably not the easiest way to learn. Also, if you do not wish to be restricted by the terms of GNU GPL you might want to avoid copying this code.

7Visit Ogg and Vorbis respective web pages at http://xiph.org/ogg/ and http://xiph.org/vorbis/.

8See http://xiph.org/vorbis/doc/Vorbis_I_spec.html#vorbis-spec-comment for more information.

9The Vorbis header is three headers: first a small initial packet with the first page of basic parameters, a second packet with bitstream comments and a thirt packet that holds the codebook.


Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.