Question

Hey there stack overflow.

I'm creating a playlist player in Java, so far so good, I got all the logic down and the project is nearing completion. We've been testing the playback by creating some large playlist and just let the thing go from start to end. The playback sounds good, but sometimes the audio is cut off at the end. This happens very rarely. The last x seconds (time varies) are not played.

The files im testing with are all PCM wave file of 16 or 24 bit sampling size. Im using the Java sound engine in combination with Java zooms mp3 and ogg spi to support other types of audio files.

So far I have this logged a couple of times and my first thought was that the file might be corrupt, this is not the case. I've tried playing the file on its own and it played fully!

I've tried to find the problem but i just cant find it. I dont think theres anything wrong with my audio player, im running out of ideas.

Here is how i create my audio input stream:

public static AudioInputStream getUnmarkableAudioInputStream(Mixer mixer, File file)
        throws UnsupportedAudioFileException
{
    if (!file.exists() || !file.canRead()) {
        return null;
    }

    AudioInputStream stream;
    try {
        stream = getAudioInputStream(file);
    } catch (IOException e) {
        logger.error("failed to retrieve stream from file", e);
        return null;

    }

    AudioFormat baseFormat = stream.getFormat();

    DataLine.Info info = new DataLine.Info(SourceDataLine.class, baseFormat);
    boolean supportedDirectly = false;
    if (mixer == null) {
        supportedDirectly = AudioSystem.isLineSupported(info);
    } else {
        supportedDirectly = mixer.isLineSupported(info);
    }

    // compare the AudioFormat with the desired one
    if (baseFormat.getEncoding() != AudioFormat.Encoding.PCM_SIGNED || !supportedDirectly) {
        AudioFormat decodedFormat = new AudioFormat(
                AudioFormat.Encoding.PCM_SIGNED,
                baseFormat.getSampleRate(), 16, baseFormat.getChannels(),
                baseFormat.getChannels() * 2, baseFormat.getSampleRate(),
                false);

        // convert the audio format to the supported one
        if (AudioSystem.isConversionSupported(decodedFormat, baseFormat)) {
            stream = AudioSystem.getAudioInputStream(decodedFormat, stream);
        } else {
            logger.debug(
                    "Audio format {} is not supported "
                            + "and can not be converted to default format",
                    baseFormat.toString());
            return null;
        }
    }
    return stream;
}

And this is my audio player thread:

final class PlayerThread extends Thread
{

    private byte[] buffer;

    /**
     * Initialize the buffer
     */
    public void initBuffer()
    {
        linelock.lock();
        try {
            buffer = new byte[line.getBufferSize() / 5];
        } finally {
            linelock.unlock();
        }
    }

    public void run()
    {
        initBuffer();
        while (!isInterrupted()) {
            checkState();

            // if the line is just cleared go to the start of the loop
            if (line == null || isInterrupted()) {
                continue;
            }

            write();
        }

        // clean up all resources
        close();

        // change the state
        state = Player.State.STOPPED;
    }

    private void checkState()
    {
        if (state != Player.State.PLAYING) {
            if (line != null) {
                line.flush();
            }

            try {
                synchronized (this) {
                    this.wait();
                }
            } catch (InterruptedException e) {
                // reset the interupt status
                interrupt();
            }
        }
    }

    private void write()
    {
        // how much bytes could be written on the line
        int available = line.available();

        // is the space on the line big enough to write the buffer to
        if (available >= buffer.length) {
            // fill the buffer array
            int read = 0;
            try {
                read = audioStream.read(buffer, 0, buffer.length);
            } catch (Throwable ball) {
                logger.error("Error in audio engine (read)", ball);
            }

            // if there was something to read, write it to the line
            // otherwise stop the player
            if (read >= 0) {
                try {
                    linelock.lock();
                    line.write(buffer, 0, read);
                } catch (Throwable ball) {
                    logger.error("Error in audio engine (write)", ball);
                } finally {
                    linelock.unlock();
                }
                bytesRead += read;
            } else {
                line.drain();
                MoreDefaultPlayer.this.stop();
            }
        }
    }

    private void close()
    {
        // invoke close on listeners
        invokePlayerClosedOnListeners();

        // destroy the volume chain
        vc.removeVolumeListener(MoreDefaultPlayer.this);

        // close the stream
        try {
            audioStream.close();
        } catch (IOException e) {
            logger.error("failed to close audio stream");
        }

        clearAllListeners();

        linelock.lock();
        try {
            // quit the line
            line.stop();
            line.close();
            line = null;
        } finally {
            linelock.unlock();
        }
    }
}

As you can see I drain the line after, so i dont think the problem is the line being closed before everything from the stream is played.
Can anyone see what might be wrong with this code?

Was it helpful?

Solution

I don't see an obvious answer, but there are a couple things that raise yellow flags for me. The common practise is to put the line.write() method in a while loop, not to invoke it repeatedly. There is usually no need to test for line.available() or to handle locking the line. The method line.write() will handle the necessary blocking if there is no space on the line available. I've always been cautioned not to lock or block audio lines unnecessarily.

Is the locking logic an integral part of the handling of the sequence of queues? The error you are describing could be in that handling. (Maybe there is an interaction with the test of available() compared to the buffer size? Is the amount of cutoff roughly equal to the buffer size?)

I would consider implementing a LineListener to announce when a cue is finished, and making that event the trigger of the playback of the next cue. An LineEvent of type STOP can be issued when the given file is done, notifying whatever handles the queue to proceed to the next file.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top