Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f509e72c authored by Narayan Kamath's avatar Narayan Kamath Committed by Android (Google) Code Review
Browse files

Merge "Add a separate thread for writing to audiotracks."

parents ceb82f71 c90f1c81
Loading
Loading
Loading
Loading
+64 −25
Original line number Original line Diff line number Diff line
@@ -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;


/**
/**
@@ -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
@@ -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
@@ -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;
@@ -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;
+7 −1
Original line number Original line Diff line number Diff line
@@ -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;


@@ -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
@@ -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();
@@ -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) {