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