167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath// Copyright 2011 Google Inc. All Rights Reserved.
267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamathpackage android.speech.tts;
467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamathimport android.media.AudioFormat;
667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamathimport android.media.AudioTrack;
75cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniakimport android.speech.tts.TextToSpeechService.AudioOutputParams;
867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamathimport android.util.Log;
967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
1067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath/**
1167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath * Exposes parts of the {@link AudioTrack} API by delegating calls to an
1267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath * underlying {@link AudioTrack}. Additionally, provides methods like
1367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath * {@link #waitAndRelease()} that will block until all audiotrack
1467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath * data has been flushed to the mixer, and is estimated to have completed
1567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath * playback.
1667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath */
1767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamathclass BlockingAudioTrack {
1867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private static final String TAG = "TTS.BlockingAudioTrack";
1967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private static final boolean DBG = false;
2067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
2167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
2267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    /**
2367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * The minimum increment of time to wait for an AudioTrack to finish
2467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * playing.
2567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     */
2667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private static final long MIN_SLEEP_TIME_MS = 20;
2767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
2867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    /**
2967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * The maximum increment of time to sleep while waiting for an AudioTrack
3067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * to finish playing.
3167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     */
3267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private static final long MAX_SLEEP_TIME_MS = 2500;
3367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
3467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    /**
3567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * The maximum amount of time to wait for an audio track to make progress while
3667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * it remains in PLAYSTATE_PLAYING. This should never happen in normal usage, but
3767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * could happen in exceptional circumstances like a media_server crash.
3867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     */
3967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private static final long MAX_PROGRESS_WAIT_MS = MAX_SLEEP_TIME_MS;
4067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
4167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    /**
4267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * Minimum size of the buffer of the underlying {@link android.media.AudioTrack}
4367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * we create.
4467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     */
4567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
4667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
4767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
485cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak    private final AudioOutputParams mAudioParams;
4967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private final int mSampleRateInHz;
5067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private final int mAudioFormat;
5167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private final int mChannelCount;
525cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak
5367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
5467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private final int mBytesPerFrame;
5567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    /**
5667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * A "short utterance" is one that uses less bytes than the audio
5767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * track buffer size (mAudioBufferSize). In this case, we need to call
5867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * {@link AudioTrack#stop()} to send pending buffers to the mixer, and slightly
5967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * different logic is required to wait for the track to finish.
6067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     *
6167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * Not volatile, accessed only from the audio playback thread.
6267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     */
6367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private boolean mIsShortUtterance;
6467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    /**
6567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     * Will be valid after a call to {@link #init()}.
6667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath     */
6767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private int mAudioBufferSize;
6867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private int mBytesWritten = 0;
6967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
70ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath    // Need to be seen by stop() which can be called from another thread. mAudioTrack will be
71ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath    // set to null only after waitAndRelease().
7270574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak    private Object mAudioTrackLock = new Object();
7370574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak    private AudioTrack mAudioTrack;
7467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private volatile boolean mStopped;
7567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
765cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak    private int mSessionId;
775cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak
785cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak    BlockingAudioTrack(AudioOutputParams audioParams, int sampleRate,
795cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak            int audioFormat, int channelCount) {
805cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak        mAudioParams = audioParams;
8167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mSampleRateInHz = sampleRate;
8267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mAudioFormat = audioFormat;
8367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mChannelCount = channelCount;
8467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
8534a37bdebb3d606dac7c7d1dd7a0effdb59bd3d6Glenn Kasten        mBytesPerFrame = AudioFormat.getBytesPerSample(mAudioFormat) * mChannelCount;
8667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mIsShortUtterance = false;
8767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mAudioBufferSize = 0;
8867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mBytesWritten = 0;
8967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
9067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mAudioTrack = null;
9167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mStopped = false;
9267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
9367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
94ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath    public boolean init() {
9567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        AudioTrack track = createStreamingAudioTrack();
9670574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        synchronized (mAudioTrackLock) {
9770574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak            mAudioTrack = track;
9870574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        }
9967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
100ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath        if (track == null) {
101ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath            return false;
102ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath        } else {
103ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath            return true;
10467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
10567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
10667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
10767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    public void stop() {
10870574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        synchronized (mAudioTrackLock) {
10970574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak            if (mAudioTrack != null) {
11070574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak                mAudioTrack.stop();
11170574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak            }
11270574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak            mStopped = true;
11367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
11467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
11567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
11667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    public int write(byte[] data) {
11770574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        AudioTrack track = null;
11870574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        synchronized (mAudioTrackLock) {
11970574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak            track = mAudioTrack;
12070574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        }
12170574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak
12270574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        if (track == null || mStopped) {
12367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            return -1;
12467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
12570574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        final int bytesWritten = writeToAudioTrack(track, data);
12670574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak
12767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mBytesWritten += bytesWritten;
12867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        return bytesWritten;
12967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
13067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
13167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    public void waitAndRelease() {
13270574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        AudioTrack track = null;
13370574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        synchronized (mAudioTrackLock) {
13470574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak            track = mAudioTrack;
13570574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        }
136ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath        if (track == null) {
137ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath            if (DBG) Log.d(TAG, "Audio track null [duplicate call to waitAndRelease ?]");
138ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath            return;
139ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath        }
140ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath
14167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        // For "small" audio tracks, we have to stop() them to make them mixable,
14267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        // else the audio subsystem will wait indefinitely for us to fill the buffer
14367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        // before rendering the track mixable.
14467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        //
14567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        // If mStopped is true, the track would already have been stopped, so not
14667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        // much point not doing that again.
14767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (mBytesWritten < mAudioBufferSize && !mStopped) {
14867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            if (DBG) {
14967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                Log.d(TAG, "Stopping audio track to flush audio, state was : " +
150ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath                        track.getPlayState() + ",stopped= " + mStopped);
15167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            }
15267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
15367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            mIsShortUtterance = true;
154ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath            track.stop();
15567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
15667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
15767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        // Block until the audio track is done only if we haven't stopped yet.
15867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (!mStopped) {
15967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            if (DBG) Log.d(TAG, "Waiting for audio track to complete : " + mAudioTrack.hashCode());
16067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            blockUntilDone(mAudioTrack);
16167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
16267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
16367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        // The last call to AudioTrack.write( ) will return only after
16467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        // all data from the audioTrack has been sent to the mixer, so
16567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        // it's safe to release at this point.
166ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath        if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]");
16765c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts        synchronized (mAudioTrackLock) {
16870574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak            mAudioTrack = null;
16970574efd8f7105eb73a6ffe31992ed3c5971c8ccPrzemyslaw Szczepaniak        }
170ed4e541a20cc662b8399844684d18ad0060bd1cbNarayan Kamath        track.release();
17167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
17267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
17367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
17467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    static int getChannelConfig(int channelCount) {
17567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (channelCount == 1) {
17667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            return AudioFormat.CHANNEL_OUT_MONO;
17767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        } else if (channelCount == 2){
17867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            return AudioFormat.CHANNEL_OUT_STEREO;
17967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
18067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
18167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        return 0;
18267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
18367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
18467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    long getAudioLengthMs(int numBytes) {
18567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        final int unconsumedFrames = numBytes / mBytesPerFrame;
18667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        final long estimatedTimeMs = unconsumedFrames * 1000 / mSampleRateInHz;
18767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
18867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        return estimatedTimeMs;
18967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
19067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
19167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private static int writeToAudioTrack(AudioTrack audioTrack, byte[] bytes) {
19267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
19367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            if (DBG) Log.d(TAG, "AudioTrack not playing, restarting : " + audioTrack.hashCode());
19467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            audioTrack.play();
19567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
19667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
19767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        int count = 0;
19867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        while (count < bytes.length) {
19967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // Note that we don't take bufferCopy.mOffset into account because
20067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // it is guaranteed to be 0.
20167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            int written = audioTrack.write(bytes, count, bytes.length);
20267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            if (written <= 0) {
20367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                break;
20467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            }
20567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            count += written;
20667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
20767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        return count;
20867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
20967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
21067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private AudioTrack createStreamingAudioTrack() {
21167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        final int channelConfig = getChannelConfig(mChannelCount);
21267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
21367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        int minBufferSizeInBytes
21467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                = AudioTrack.getMinBufferSize(mSampleRateInHz, channelConfig, mAudioFormat);
21567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        int bufferSizeInBytes = Math.max(MIN_AUDIO_BUFFER_SIZE, minBufferSizeInBytes);
21667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
217672695e42387a024083d198f3d665f354ef1d27cPrzemyslaw Szczepaniak        AudioFormat audioFormat = (new AudioFormat.Builder())
218672695e42387a024083d198f3d665f354ef1d27cPrzemyslaw Szczepaniak                .setChannelMask(channelConfig)
219672695e42387a024083d198f3d665f354ef1d27cPrzemyslaw Szczepaniak                .setEncoding(mAudioFormat)
220672695e42387a024083d198f3d665f354ef1d27cPrzemyslaw Szczepaniak                .setSampleRate(mSampleRateInHz).build();
221672695e42387a024083d198f3d665f354ef1d27cPrzemyslaw Szczepaniak        AudioTrack audioTrack = new AudioTrack(mAudioParams.mAudioAttributes,
222672695e42387a024083d198f3d665f354ef1d27cPrzemyslaw Szczepaniak                audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM,
2235cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak                mAudioParams.mSessionId);
224672695e42387a024083d198f3d665f354ef1d27cPrzemyslaw Szczepaniak
22567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
22667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            Log.w(TAG, "Unable to create audio track.");
22767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            audioTrack.release();
22867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            return null;
22967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
23067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
23167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        mAudioBufferSize = bufferSizeInBytes;
23267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
2335cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak        setupVolume(audioTrack, mAudioParams.mVolume, mAudioParams.mPan);
23467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        return audioTrack;
23567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
23667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
23767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private void blockUntilDone(AudioTrack audioTrack) {
23867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (mBytesWritten <= 0) {
23967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            return;
24067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
24167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
24267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (mIsShortUtterance) {
24367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // In this case we would have called AudioTrack#stop() to flush
24467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // buffers to the mixer. This makes the playback head position
24567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // unobservable and notification markers do not work reliably. We
24667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // have no option but to wait until we think the track would finish
24767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // playing and release it after.
24867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            //
24967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // This isn't as bad as it looks because (a) We won't end up waiting
25067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // for much longer than we should because even at 4khz mono, a short
25167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // utterance weighs in at about 2 seconds, and (b) such short utterances
25267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // are expected to be relatively infrequent and in a stream of utterances
25367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // this shows up as a slightly longer pause.
25467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            blockUntilEstimatedCompletion();
25567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        } else {
25667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            blockUntilCompletion(audioTrack);
25767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
25867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
25967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
26067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private void blockUntilEstimatedCompletion() {
26167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        final int lengthInFrames = mBytesWritten / mBytesPerFrame;
26267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        final long estimatedTimeMs = (lengthInFrames * 1000 / mSampleRateInHz);
26367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
26467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (DBG) Log.d(TAG, "About to sleep for: " + estimatedTimeMs + "ms for a short utterance");
26567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
26667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        try {
26767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            Thread.sleep(estimatedTimeMs);
26867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        } catch (InterruptedException ie) {
26967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // Do nothing.
27067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
27167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
27267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
27367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private void blockUntilCompletion(AudioTrack audioTrack) {
27467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        final int lengthInFrames = mBytesWritten / mBytesPerFrame;
27567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
27667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        int previousPosition = -1;
27767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        int currentPosition = 0;
27867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        long blockedTimeMs = 0;
27967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
28067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        while ((currentPosition = audioTrack.getPlaybackHeadPosition()) < lengthInFrames &&
28167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING && !mStopped) {
28267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
28367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            final long estimatedTimeMs = ((lengthInFrames - currentPosition) * 1000) /
28467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                    audioTrack.getSampleRate();
28567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            final long sleepTimeMs = clip(estimatedTimeMs, MIN_SLEEP_TIME_MS, MAX_SLEEP_TIME_MS);
28667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
28767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // Check if the audio track has made progress since the last loop
28867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // iteration. We should then add in the amount of time that was
28967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            // spent sleeping in the last iteration.
29067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            if (currentPosition == previousPosition) {
29167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                // This works only because the sleep time that would have been calculated
29267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                // would be the same in the previous iteration too.
29367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                blockedTimeMs += sleepTimeMs;
29467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                // If we've taken too long to make progress, bail.
29567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                if (blockedTimeMs > MAX_PROGRESS_WAIT_MS) {
29667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                    Log.w(TAG, "Waited unsuccessfully for " + MAX_PROGRESS_WAIT_MS + "ms " +
29767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                            "for AudioTrack to make progress, Aborting");
29867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                    break;
29967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                }
30067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            } else {
30167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                blockedTimeMs = 0;
30267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            }
30367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            previousPosition = currentPosition;
30467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
30567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            if (DBG) {
30667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                Log.d(TAG, "About to sleep for : " + sleepTimeMs + " ms," +
30767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                        " Playback position : " + currentPosition + ", Length in frames : "
30867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                        + lengthInFrames);
30967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            }
31067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            try {
31167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                Thread.sleep(sleepTimeMs);
31267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            } catch (InterruptedException ie) {
31367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath                break;
31467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            }
31567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
31667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
31767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
31867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private static void setupVolume(AudioTrack audioTrack, float volume, float pan) {
31967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        final float vol = clip(volume, 0.0f, 1.0f);
32067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        final float panning = clip(pan, -1.0f, 1.0f);
32167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
32267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        float volLeft = vol;
32367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        float volRight = vol;
32467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (panning > 0.0f) {
32567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            volLeft *= (1.0f - panning);
32667ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        } else if (panning < 0.0f) {
32767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            volRight *= (1.0f + panning);
32867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
32967ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (DBG) Log.d(TAG, "volLeft=" + volLeft + ",volRight=" + volRight);
33067ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        if (audioTrack.setStereoVolume(volLeft, volRight) != AudioTrack.SUCCESS) {
33167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath            Log.e(TAG, "Failed to set volume");
33267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath        }
33367ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
33467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
33567ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    private static final long clip(long value, long min, long max) {
3365cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak        return value < min ? min : (value < max ? value : max);
33767ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
33867ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
3395cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak    private static final float clip(float value, float min, float max) {
3405cbf17ca053b09beadd0b031a46ce193ab27a0f8Przemyslaw Szczepaniak        return value < min ? min : (value < max ? value : max);
34167ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath    }
34267ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath
34365c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts    /**
34465c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts     * @see
34565c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts     *     AudioTrack#setPlaybackPositionUpdateListener(AudioTrack.OnPlaybackPositionUpdateListener).
34665c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts     */
34765c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts    public void setPlaybackPositionUpdateListener(
34865c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts            AudioTrack.OnPlaybackPositionUpdateListener listener) {
34965c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts        synchronized (mAudioTrackLock) {
35065c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts            if (mAudioTrack != null) {
35165c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts                mAudioTrack.setPlaybackPositionUpdateListener(listener);
35265c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts            }
35365c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts        }
35465c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts    }
35565c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts
35665c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts    /** @see AudioTrack#setNotificationMarkerPosition(int). */
35765c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts    public void setNotificationMarkerPosition(int frames) {
35865c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts        synchronized (mAudioTrackLock) {
35965c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts            if (mAudioTrack != null) {
36065c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts                mAudioTrack.setNotificationMarkerPosition(frames);
36165c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts            }
36265c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts        }
36365c50784564d0bae9276fde5472dd8898a781bcdNiels Egberts    }
36467ae6bc83cf2b30b0c61b9ebba5fed7a0038549cNarayan Kamath}
365