Loading core/java/android/speech/tts/PlaybackSynthesisRequest.java +64 −25 Original line number Original line Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.speech.tts; import android.media.AudioFormat; import android.media.AudioFormat; import android.media.AudioTrack; import android.media.AudioTrack; import android.os.Bundle; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.util.Log; /** /** Loading Loading @@ -49,16 +50,20 @@ class PlaybackSynthesisRequest extends SynthesisRequest { private final float mPan; private final float mPan; private final Object mStateLock = new Object(); private final Object mStateLock = new Object(); private AudioTrack mAudioTrack = null; private final Handler mAudioTrackHandler; private volatile AudioTrack mAudioTrack = null; private boolean mStopped = false; private boolean mStopped = false; private boolean mDone = false; private boolean mDone = false; private volatile boolean mWriteErrorOccured; PlaybackSynthesisRequest(String text, Bundle params, PlaybackSynthesisRequest(String text, Bundle params, int streamType, float volume, float pan) { int streamType, float volume, float pan, Handler audioTrackHandler) { super(text, params); super(text, params); mStreamType = streamType; mStreamType = streamType; mVolume = volume; mVolume = volume; mPan = pan; mPan = pan; mAudioTrackHandler = audioTrackHandler; mWriteErrorOccured = false; } } @Override @Override Loading @@ -70,14 +75,28 @@ class PlaybackSynthesisRequest extends SynthesisRequest { } } } } // Always guarded by mStateLock. private void cleanUp() { private void cleanUp() { if (DBG) Log.d(TAG, "cleanUp()"); if (DBG) Log.d(TAG, "cleanUp()"); if (mAudioTrack != null) { if (mAudioTrack == null) { mAudioTrack.flush(); return; mAudioTrack.stop(); } mAudioTrack.release(); final AudioTrack audioTrack = mAudioTrack; mAudioTrack = null; mAudioTrack = null; // Clean up on the audiotrack handler thread. // // NOTE: It isn't very clear whether AudioTrack is thread safe. // If it is we can clean up on the current (synthesis) thread. mAudioTrackHandler.post(new Runnable() { @Override public void run() { audioTrack.flush(); audioTrack.stop(); audioTrack.release(); } } }); } } @Override @Override Loading Loading @@ -146,10 +165,15 @@ class PlaybackSynthesisRequest extends SynthesisRequest { Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length + ")"); + offset + "," + length + ")"); } } if (length > getMaxBufferSize()) { if (length > getMaxBufferSize() || length <= 0) { throw new IllegalArgumentException("buffer is too large (" + length + " bytes)"); throw new IllegalArgumentException("buffer is too large or of zero length (" + + length + " bytes)"); } } synchronized (mStateLock) { synchronized (mStateLock) { if (mWriteErrorOccured) { if (DBG) Log.d(TAG, "Error writing to audio track, count < 0"); return TextToSpeech.ERROR; } if (mStopped) { if (mStopped) { if (DBG) Log.d(TAG, "Request has been aborted."); if (DBG) Log.d(TAG, "Request has been aborted."); return TextToSpeech.ERROR; return TextToSpeech.ERROR; Loading @@ -158,29 +182,44 @@ class PlaybackSynthesisRequest extends SynthesisRequest { Log.e(TAG, "audioAvailable(): Not started"); Log.e(TAG, "audioAvailable(): Not started"); return TextToSpeech.ERROR; return TextToSpeech.ERROR; } } int playState = mAudioTrack.getPlayState(); final AudioTrack audioTrack = mAudioTrack; // Sigh, another copy. final byte[] bufferCopy = new byte[length]; System.arraycopy(buffer, offset, bufferCopy, 0, length); mAudioTrackHandler.post(new Runnable() { @Override public void run() { int playState = audioTrack.getPlayState(); if (playState == AudioTrack.PLAYSTATE_STOPPED) { if (playState == AudioTrack.PLAYSTATE_STOPPED) { if (DBG) Log.d(TAG, "AudioTrack stopped, restarting"); if (DBG) Log.d(TAG, "AudioTrack stopped, restarting"); mAudioTrack.play(); audioTrack.play(); } } // TODO: loop until all data is written? // TODO: loop until all data is written? if (DBG) Log.d(TAG, "AudioTrack.write()"); if (DBG) Log.d(TAG, "AudioTrack.write()"); int count = mAudioTrack.write(buffer, offset, length); int count = audioTrack.write(bufferCopy, 0, bufferCopy.length); if (DBG) Log.d(TAG, "AudioTrack.write() returned " + count); // The semantics of this change very slightly. Earlier, we would // report an error immediately, Now we will return an error on // the next API call, usually done( ) or another audioAvailable( ) // call. if (count < 0) { if (count < 0) { Log.e(TAG, "Writing to AudioTrack failed: " + count); mWriteErrorOccured = true; cleanUp(); return TextToSpeech.ERROR; } else { return TextToSpeech.SUCCESS; } } } } }); return TextToSpeech.SUCCESS; } } } @Override @Override public int done() { public int done() { if (DBG) Log.d(TAG, "done()"); if (DBG) Log.d(TAG, "done()"); synchronized (mStateLock) { synchronized (mStateLock) { if (mWriteErrorOccured) { if (DBG) Log.d(TAG, "Error writing to audio track, count < 0"); return TextToSpeech.ERROR; } if (mStopped) { if (mStopped) { if (DBG) Log.d(TAG, "Request has been aborted."); if (DBG) Log.d(TAG, "Request has been aborted."); return TextToSpeech.ERROR; return TextToSpeech.ERROR; Loading core/java/android/speech/tts/TextToSpeechService.java +7 −1 Original line number Original line Diff line number Diff line Loading @@ -51,6 +51,7 @@ public abstract class TextToSpeechService extends Service { private static final String SYNTH_THREAD_NAME = "SynthThread"; private static final String SYNTH_THREAD_NAME = "SynthThread"; private SynthHandler mSynthHandler; private SynthHandler mSynthHandler; private Handler mAudioTrackHandler; private CallbackMap mCallbacks; private CallbackMap mCallbacks; Loading @@ -63,6 +64,10 @@ public abstract class TextToSpeechService extends Service { synthThread.start(); synthThread.start(); mSynthHandler = new SynthHandler(synthThread.getLooper()); mSynthHandler = new SynthHandler(synthThread.getLooper()); HandlerThread audioTrackThread = new HandlerThread("TTS.audioTrackThread"); audioTrackThread.start(); mAudioTrackHandler = new Handler(audioTrackThread.getLooper()); mCallbacks = new CallbackMap(); mCallbacks = new CallbackMap(); // Load default language // Load default language Loading @@ -75,6 +80,7 @@ public abstract class TextToSpeechService extends Service { // Tell the synthesizer to stop // Tell the synthesizer to stop mSynthHandler.quit(); mSynthHandler.quit(); mAudioTrackHandler.getLooper().quit(); // Unregister all callbacks. // Unregister all callbacks. mCallbacks.kill(); mCallbacks.kill(); Loading Loading @@ -444,7 +450,7 @@ public abstract class TextToSpeechService extends Service { protected SynthesisRequest createSynthesisRequest() { protected SynthesisRequest createSynthesisRequest() { return new PlaybackSynthesisRequest(mText, mParams, return new PlaybackSynthesisRequest(mText, mParams, getStreamType(), getVolume(), getPan()); getStreamType(), getVolume(), getPan(), mAudioTrackHandler); } } private void setRequestParams(SynthesisRequest request) { private void setRequestParams(SynthesisRequest request) { Loading Loading
core/java/android/speech/tts/PlaybackSynthesisRequest.java +64 −25 Original line number Original line Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.speech.tts; import android.media.AudioFormat; import android.media.AudioFormat; import android.media.AudioTrack; import android.media.AudioTrack; import android.os.Bundle; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.util.Log; /** /** Loading Loading @@ -49,16 +50,20 @@ class PlaybackSynthesisRequest extends SynthesisRequest { private final float mPan; private final float mPan; private final Object mStateLock = new Object(); private final Object mStateLock = new Object(); private AudioTrack mAudioTrack = null; private final Handler mAudioTrackHandler; private volatile AudioTrack mAudioTrack = null; private boolean mStopped = false; private boolean mStopped = false; private boolean mDone = false; private boolean mDone = false; private volatile boolean mWriteErrorOccured; PlaybackSynthesisRequest(String text, Bundle params, PlaybackSynthesisRequest(String text, Bundle params, int streamType, float volume, float pan) { int streamType, float volume, float pan, Handler audioTrackHandler) { super(text, params); super(text, params); mStreamType = streamType; mStreamType = streamType; mVolume = volume; mVolume = volume; mPan = pan; mPan = pan; mAudioTrackHandler = audioTrackHandler; mWriteErrorOccured = false; } } @Override @Override Loading @@ -70,14 +75,28 @@ class PlaybackSynthesisRequest extends SynthesisRequest { } } } } // Always guarded by mStateLock. private void cleanUp() { private void cleanUp() { if (DBG) Log.d(TAG, "cleanUp()"); if (DBG) Log.d(TAG, "cleanUp()"); if (mAudioTrack != null) { if (mAudioTrack == null) { mAudioTrack.flush(); return; mAudioTrack.stop(); } mAudioTrack.release(); final AudioTrack audioTrack = mAudioTrack; mAudioTrack = null; mAudioTrack = null; // Clean up on the audiotrack handler thread. // // NOTE: It isn't very clear whether AudioTrack is thread safe. // If it is we can clean up on the current (synthesis) thread. mAudioTrackHandler.post(new Runnable() { @Override public void run() { audioTrack.flush(); audioTrack.stop(); audioTrack.release(); } } }); } } @Override @Override Loading Loading @@ -146,10 +165,15 @@ class PlaybackSynthesisRequest extends SynthesisRequest { Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length + ")"); + offset + "," + length + ")"); } } if (length > getMaxBufferSize()) { if (length > getMaxBufferSize() || length <= 0) { throw new IllegalArgumentException("buffer is too large (" + length + " bytes)"); throw new IllegalArgumentException("buffer is too large or of zero length (" + + length + " bytes)"); } } synchronized (mStateLock) { synchronized (mStateLock) { if (mWriteErrorOccured) { if (DBG) Log.d(TAG, "Error writing to audio track, count < 0"); return TextToSpeech.ERROR; } if (mStopped) { if (mStopped) { if (DBG) Log.d(TAG, "Request has been aborted."); if (DBG) Log.d(TAG, "Request has been aborted."); return TextToSpeech.ERROR; return TextToSpeech.ERROR; Loading @@ -158,29 +182,44 @@ class PlaybackSynthesisRequest extends SynthesisRequest { Log.e(TAG, "audioAvailable(): Not started"); Log.e(TAG, "audioAvailable(): Not started"); return TextToSpeech.ERROR; return TextToSpeech.ERROR; } } int playState = mAudioTrack.getPlayState(); final AudioTrack audioTrack = mAudioTrack; // Sigh, another copy. final byte[] bufferCopy = new byte[length]; System.arraycopy(buffer, offset, bufferCopy, 0, length); mAudioTrackHandler.post(new Runnable() { @Override public void run() { int playState = audioTrack.getPlayState(); if (playState == AudioTrack.PLAYSTATE_STOPPED) { if (playState == AudioTrack.PLAYSTATE_STOPPED) { if (DBG) Log.d(TAG, "AudioTrack stopped, restarting"); if (DBG) Log.d(TAG, "AudioTrack stopped, restarting"); mAudioTrack.play(); audioTrack.play(); } } // TODO: loop until all data is written? // TODO: loop until all data is written? if (DBG) Log.d(TAG, "AudioTrack.write()"); if (DBG) Log.d(TAG, "AudioTrack.write()"); int count = mAudioTrack.write(buffer, offset, length); int count = audioTrack.write(bufferCopy, 0, bufferCopy.length); if (DBG) Log.d(TAG, "AudioTrack.write() returned " + count); // The semantics of this change very slightly. Earlier, we would // report an error immediately, Now we will return an error on // the next API call, usually done( ) or another audioAvailable( ) // call. if (count < 0) { if (count < 0) { Log.e(TAG, "Writing to AudioTrack failed: " + count); mWriteErrorOccured = true; cleanUp(); return TextToSpeech.ERROR; } else { return TextToSpeech.SUCCESS; } } } } }); return TextToSpeech.SUCCESS; } } } @Override @Override public int done() { public int done() { if (DBG) Log.d(TAG, "done()"); if (DBG) Log.d(TAG, "done()"); synchronized (mStateLock) { synchronized (mStateLock) { if (mWriteErrorOccured) { if (DBG) Log.d(TAG, "Error writing to audio track, count < 0"); return TextToSpeech.ERROR; } if (mStopped) { if (mStopped) { if (DBG) Log.d(TAG, "Request has been aborted."); if (DBG) Log.d(TAG, "Request has been aborted."); return TextToSpeech.ERROR; return TextToSpeech.ERROR; Loading
core/java/android/speech/tts/TextToSpeechService.java +7 −1 Original line number Original line Diff line number Diff line Loading @@ -51,6 +51,7 @@ public abstract class TextToSpeechService extends Service { private static final String SYNTH_THREAD_NAME = "SynthThread"; private static final String SYNTH_THREAD_NAME = "SynthThread"; private SynthHandler mSynthHandler; private SynthHandler mSynthHandler; private Handler mAudioTrackHandler; private CallbackMap mCallbacks; private CallbackMap mCallbacks; Loading @@ -63,6 +64,10 @@ public abstract class TextToSpeechService extends Service { synthThread.start(); synthThread.start(); mSynthHandler = new SynthHandler(synthThread.getLooper()); mSynthHandler = new SynthHandler(synthThread.getLooper()); HandlerThread audioTrackThread = new HandlerThread("TTS.audioTrackThread"); audioTrackThread.start(); mAudioTrackHandler = new Handler(audioTrackThread.getLooper()); mCallbacks = new CallbackMap(); mCallbacks = new CallbackMap(); // Load default language // Load default language Loading @@ -75,6 +80,7 @@ public abstract class TextToSpeechService extends Service { // Tell the synthesizer to stop // Tell the synthesizer to stop mSynthHandler.quit(); mSynthHandler.quit(); mAudioTrackHandler.getLooper().quit(); // Unregister all callbacks. // Unregister all callbacks. mCallbacks.kill(); mCallbacks.kill(); Loading Loading @@ -444,7 +450,7 @@ public abstract class TextToSpeechService extends Service { protected SynthesisRequest createSynthesisRequest() { protected SynthesisRequest createSynthesisRequest() { return new PlaybackSynthesisRequest(mText, mParams, return new PlaybackSynthesisRequest(mText, mParams, getStreamType(), getVolume(), getPan()); getStreamType(), getVolume(), getPan(), mAudioTrackHandler); } } private void setRequestParams(SynthesisRequest request) { private void setRequestParams(SynthesisRequest request) { Loading