Android AudioTrack getPlaybackHeadPosition always returns 0

ScottF :

I am writing some code that intends to take a Wave file, and write it out to and AudioTrack in mode stream. This is a minimum viable test to get AudioTrack stream mode working.

But once I write some buffer of audio to the AudioTrack, and subsequently call play(), the method getPlaybackHeadPosition() continually returns 0.

EDIT: If I ignore my available frames check, and just continually write buffers to the AudioTrack, the write method returns 0 (after the the first buffer write), indicating that it simply did not write any more audio. So it seems that the AudioTrack just doesn't want to start playing.

My code is properly priming the audiotrack. The play method is not throwing any exceptions, so I am not sure what is going wrong.

When stepping through the code, everything on my end is exactly how I anticipate it, so I am thinking somehow I have the AudioTrack configured wrong.

I am running on an emulator, but I don't think that should be an issue.

The WavFile class I am using is a vetted class that I have up and running reliably in lots of Java projects, it is tested to work well.

Observe the following log write, which is a snippet from the larger chunk of code. This log write is never hitting...

                if (headPosition > 0)
                    Log.e("headPosition is greater than zero!!");

..

public static void writeToAudioTrackStream(final WavFile wave) 
    {
    Log.e("writeToAudioTrackStream");

    Thread thread = new Thread()
    {
        public void run()
        {
            try {

                final float[] data = wave.getData();
                int format = -1;

                if (wave.getChannel() == 1)
                    format = AudioFormat.CHANNEL_OUT_MONO;
                else if (wave.getChannel() == 2)
                    format = AudioFormat.CHANNEL_OUT_STEREO;
                else
                    throw new RuntimeException("writeToAudioTrackStatic() - unsupported number of channels value = "+wave.getChannel());

                final int bufferSizeInFrames = 2048;
                final int bytesPerSmp = wave.getBytesPerSmp();
                final int bufferSizeInBytes = bufferSizeInFrames * bytesPerSmp * wave.getChannel();

                AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, wave.getSmpRate(),
                        format,
                        AudioFormat.ENCODING_PCM_FLOAT,
                        bufferSizeInBytes,
                        AudioTrack.MODE_STREAM);

                int index = 0;
                float[] buffer = new float[bufferSizeInFrames * wave.getChannel()];
                boolean started = false;
                int framesWritten = 0;

                while (index < data.length) {

                    // calculate the available space in the buffer
                    int headPosition = audioTrack.getPlaybackHeadPosition();
                    if (headPosition > 0)
                        Log.e("headPosition is greater than zero!!");
                    int framesInBuffer = framesWritten - headPosition;
                    int availableFrames = bufferSizeInFrames - framesInBuffer;

                    // once the buffer has no space, the prime is done, so start playing
                    if (availableFrames == 0) {
                        if (!started) {
                            audioTrack.play();
                            started = true;
                        }
                        continue;
                    }

                    int endOffset = availableFrames * wave.getChannel();

                    for (int i = 0; i < endOffset; i++)
                        buffer[i] = data[index + i];

                    int samplesWritten = audioTrack.write(buffer , 0 , endOffset , AudioTrack.WRITE_BLOCKING);

                    // could return error values
                    if (samplesWritten < 0)
                        throw new RuntimeException("AudioTrack write error.");

                    framesWritten += samplesWritten / wave.getChannel();
                    index = endOffset;
                }
            }
            catch (Exception e) {
                Log.e(e.toString());
            }     
        }
    };

    thread.start();
}
greeble31 :

Per the documentation,

For portability, an application should prime the data path to the maximum allowed by writing data until the write() method returns a short transfer count. This allows play() to start immediately, and reduces the chance of underrun.

With a strict reading, this might be seen to contradict the earlier statement:

...you can optionally prime the data path prior to calling play(), by writing up to bufferSizeInBytes...

(emphasis mine), but the intent is clear enough: You're supposed to get a short write first.

This is just to get play started. Once that takes place, you can, in fact, use getPlaybackHeadPosition() to determine when more space is available. I've used that technique successfully in my own code, on many different devices/API levels.

As an aside: You should be prepared for getPlaybackHeadPosition() to change only in large increments (if I remember correctly, it's getMinBufferSize()/2). This is the max resolution available from the system; onMarkerReached() cannot be used to do any better.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=159795&siteId=1