Alan Pauley
Alan Pauley

Reputation: 121

Java - Start Audio Playback at X Position

Edit: I am using a .wav file

I'm trying to figure out how to start audio at a certain position (for example: 10 seconds into audio file rather than at the start). Reading the documentation for SourceDataLine had me believe this may be achieved using the offset during:

line.write(byte[] b, int offset, int length)

but every time I've tried any value other than 0 (the default I believe), I get java.lang.IndexOutOfBoundsException, which maybe it hasn't read x byte position yet so cannot write x byte position? I'm unsure and left scratching my head.

I figured this would be a common enough request but can't seem to find anything online related to this, only pausing and resuming audio. I'm probably not searching properly.

In case it matters, here is how I'm currently doing my audio:

     AudioInputStream stream = AudioSystem.getAudioInputStream("...file...");
     AudioFormat format = stream.getFormat();
     SourceDataLine.Info info = new DataLine.Info(SourceDataLine.class, format,((int)stream.getFrameLength()*format.getFrameSize()));
     SourceDataLine line = (SourceDataLine)AudioSystem.getLine(info);
     int bufferSize = line.getBufferSize();
     byte inBuffer[] = new byte[bufferSize];
     byte outBuffer[] = new byte[bufferSize];
     int numRead, numWritten;

     do {
         numRead = audioStream.read(inBuffer, 0, bufferSize);
            if(numRead <= 0) {
                myAudio.flushStream();
            } else {
                myAudio.writeBytesToStream(inBuffer, numRead);
            }
            do {
                numWritten = myAudio.readBytesFromStream(outBuffer, bufferSize);
                if(numWritten > 0) {
                    line.write(outBuffer, 0, numWritten);
                }
            } while(numWritten > 0);
        } while(numRead > 0);

Upvotes: 2

Views: 501

Answers (2)

thebiggestlebowski
thebiggestlebowski

Reputation: 2789

I've created an example which compiles and works. You can play a .wav file from any time point. It should also work for an mp3 file, but I haven't tested that. Invoke mp3ToWav() for that.

import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;

public class PlayWavAtTimePoint {

    public static void main(String[] args) throws Exception {
        String fileName = args[0];
        int secondsToSkip = (Integer.parseInt(args[1]));

        PlayWavAtTimePoint program = new PlayWavAtTimePoint();
        AudioInputStream is = program.getAudioInputStream(fileName);
        program.skipFromBeginning(is, secondsToSkip);
        program.playSound(is);
    }

    private static void skipFromBeginning(AudioInputStream audioStream, int secondsToSkip) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
        AudioFormat format = audioStream.getFormat();

        // find out how many bytes you have to skip, this depends on bytes per frame (a.k.a. frameSize)
        long bytesToSkip = format.getFrameSize() * ((int)format.getFrameRate()) * secondsToSkip;


        // now skip until the correct number of bytes have been skipped
        long justSkipped = 0;
        while (bytesToSkip > 0 && (justSkipped = audioStream.skip(bytesToSkip)) > 0) {
            bytesToSkip -= justSkipped;
        }
    }


    private static final int BUFFER_SIZE = 128000;


    /**
     * @param filename the name of the file that is going to be played
     */
    public void playSound(String filename) throws IOException, UnsupportedAudioFileException, LineUnavailableException {
        AudioInputStream audioStream = getAudioInputStream(filename);
        playSound(audioStream);
    }

    private AudioInputStream getAudioInputStream(String filename) throws UnsupportedAudioFileException, IOException {
        return AudioSystem.getAudioInputStream(new File(filename));
    }

    public void playSound(AudioInputStream audioStream) throws LineUnavailableException, IOException {
        AudioFormat audioFormat = audioStream.getFormat();
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
        SourceDataLine audioOutput = (SourceDataLine) AudioSystem.getLine(info);
        audioOutput.open(audioFormat);
        audioOutput.start();

        //This seems to be reading the whole file into a buffer before playing ... not efficient.
        //Why not stream it?
        int nBytesRead = 0;

        byte[] abData = new byte[BUFFER_SIZE];
        while (nBytesRead != -1) {
                nBytesRead = audioStream.read(abData, 0, abData.length);
            if (nBytesRead >= 0) {
                audioOutput.write(abData, 0, nBytesRead);
            }
        }

        audioOutput.drain();
        audioOutput.close();
    }

    /**
     * Invoke this function to convert to a playable file.
     */
    public static void mp3ToWav(File mp3Data) throws UnsupportedAudioFileException, IOException {
        // open stream
        AudioInputStream mp3Stream = AudioSystem.getAudioInputStream(mp3Data);
        AudioFormat sourceFormat = mp3Stream.getFormat();
        // create audio format object for the desired stream/audio format
        // this is *not* the same as the file format (wav)
        AudioFormat convertFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                sourceFormat.getSampleRate(), 16,
                sourceFormat.getChannels(),
                sourceFormat.getChannels() * 2,
                sourceFormat.getSampleRate(),
                false);
        // create stream that delivers the desired format
        AudioInputStream converted = AudioSystem.getAudioInputStream(convertFormat, mp3Stream);
        // write stream into a file with file format wav
        AudioSystem.write(converted, AudioFileFormat.Type.WAVE, new File("/tmp/out.wav"));
    }
}

Upvotes: 0

Hendrik
Hendrik

Reputation: 5310

The problem you are having probably stems from the fact that you are adjusting the offset without adjusting the length. If your array is 10 bytes long and you are starting reading 10 bytes from offset 5 instead of 0, you are reading 5 bytes past its end.

I'd recommend to first skip the appropriate number of bytes using skip(long) on the AudioInputStream and then write to the line.

AudioInputStream stream = AudioSystem.getAudioInputStream("...file...");
AudioFormat format = stream.getFormat();
// find out how many bytes you have to skip, this depends on bytes per frame (a.k.a. frameSize)
int secondsToSkip = 10;
long bytesToSkip = format.getFrameSize() * ((int)format.getFrameRate()) * secondsToSkip;
// now skip until the correct number of bytes have been skipped
int justSkipped = 0;
while (bytesToSkip > 0 && (justSkipped = stream.skip(bytesToSkip)) > 0) {
    bytesToSkip -= justSkipped;
}
// then proceed with writing to your line like you have done before
[...]

Note that this only works, if the audio file is uncompressed. If you are dealing with something like .mp3, you first have to convert the stream to PCM (see https://stackoverflow.com/a/41850901/942774)

Upvotes: 4

Related Questions