Merge "Don't allow TTS engines to synthesize too for ahead."

This commit is contained in:
Narayan Kamath
2011-08-30 09:52:06 -07:00
committed by Android (Google) Code Review
3 changed files with 86 additions and 34 deletions

View File

@@ -390,10 +390,10 @@ class AudioPlaybackHandler {
audioTrack.play(); audioTrack.play();
} }
int count = 0; int count = 0;
while (count < bufferCopy.mLength) { while (count < bufferCopy.mBytes.length) {
// Note that we don't take bufferCopy.mOffset into account because // Note that we don't take bufferCopy.mOffset into account because
// it is guaranteed to be 0. // it is guaranteed to be 0.
int written = audioTrack.write(bufferCopy.mBytes, count, bufferCopy.mLength); int written = audioTrack.write(bufferCopy.mBytes, count, bufferCopy.mBytes.length);
if (written <= 0) { if (written <= 0) {
break; break;
} }
@@ -453,7 +453,7 @@ class AudioPlaybackHandler {
} }
final AudioTrack audioTrack = params.mAudioTrack; final AudioTrack audioTrack = params.mAudioTrack;
final int bytesPerFrame = getBytesPerFrame(params.mAudioFormat); final int bytesPerFrame = params.mBytesPerFrame;
final int lengthInBytes = params.mBytesWritten; final int lengthInBytes = params.mBytesWritten;
final int lengthInFrames = lengthInBytes / bytesPerFrame; final int lengthInFrames = lengthInBytes / bytesPerFrame;
@@ -511,16 +511,6 @@ class AudioPlaybackHandler {
return 0; return 0;
} }
static int getBytesPerFrame(int audioFormat) {
if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) {
return 1;
} else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
return 2;
}
return -1;
}
private static void setupVolume(AudioTrack audioTrack, float volume, float pan) { private static void setupVolume(AudioTrack audioTrack, float volume, float pan) {
float vol = clip(volume, 0.0f, 1.0f); float vol = clip(volume, 0.0f, 1.0f);
float panning = clip(pan, -1.0f, 1.0f); float panning = clip(pan, -1.0f, 1.0f);

View File

@@ -85,6 +85,7 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
// Note that mLogger.mError might be true too at this point. // Note that mLogger.mError might be true too at this point.
mLogger.onStopped(); mLogger.onStopped();
SynthesisMessageParams token = null;
synchronized (mStateLock) { synchronized (mStateLock) {
if (mStopped) { if (mStopped) {
Log.w(TAG, "stop() called twice"); Log.w(TAG, "stop() called twice");
@@ -97,9 +98,19 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
// In all other cases, mAudioTrackHandler.stop() will // In all other cases, mAudioTrackHandler.stop() will
// result in onComplete being called. // result in onComplete being called.
mLogger.onWriteData(); mLogger.onWriteData();
} else {
token = mToken;
} }
mStopped = true; mStopped = true;
} }
if (token != null) {
// This might result in the synthesis thread being woken up, at which
// point it will write an additional buffer to the token - but we
// won't worry about that because the audio playback queue will be cleared
// soon after (see SynthHandler#stop(String).
token.clearBuffers();
}
} }
@Override @Override
@@ -155,18 +166,22 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
+ length + " bytes)"); + length + " bytes)");
} }
SynthesisMessageParams token = null;
synchronized (mStateLock) { synchronized (mStateLock) {
if (mToken == null || mStopped) { if (mToken == null || mStopped) {
return TextToSpeech.ERROR; return TextToSpeech.ERROR;
} }
token = mToken;
// Sigh, another copy.
final byte[] bufferCopy = new byte[length];
System.arraycopy(buffer, offset, bufferCopy, 0, length);
mToken.addBuffer(bufferCopy);
mAudioTrackHandler.enqueueSynthesisDataAvailable(mToken);
} }
// Sigh, another copy.
final byte[] bufferCopy = new byte[length];
System.arraycopy(buffer, offset, bufferCopy, 0, length);
// Might block on mToken.this, if there are too many buffers waiting to
// be consumed.
token.addBuffer(bufferCopy);
mAudioTrackHandler.enqueueSynthesisDataAvailable(token);
mLogger.onEngineDataReceived(); mLogger.onEngineDataReceived();
return TextToSpeech.SUCCESS; return TextToSpeech.SUCCESS;
@@ -176,6 +191,7 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
public int done() { public int done() {
if (DBG) Log.d(TAG, "done()"); if (DBG) Log.d(TAG, "done()");
SynthesisMessageParams token = null;
synchronized (mStateLock) { synchronized (mStateLock) {
if (mDone) { if (mDone) {
Log.w(TAG, "Duplicate call to done()"); Log.w(TAG, "Duplicate call to done()");
@@ -188,9 +204,12 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
return TextToSpeech.ERROR; return TextToSpeech.ERROR;
} }
mAudioTrackHandler.enqueueSynthesisDone(mToken); token = mToken;
mLogger.onEngineComplete();
} }
mAudioTrackHandler.enqueueSynthesisDone(token);
mLogger.onEngineComplete();
return TextToSpeech.SUCCESS; return TextToSpeech.SUCCESS;
} }

View File

@@ -15,6 +15,7 @@
*/ */
package android.speech.tts; package android.speech.tts;
import android.media.AudioFormat;
import android.media.AudioTrack; import android.media.AudioTrack;
import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher; import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
@@ -24,6 +25,8 @@ import java.util.LinkedList;
* Params required to play back a synthesis request. * Params required to play back a synthesis request.
*/ */
final class SynthesisMessageParams extends MessageParams { final class SynthesisMessageParams extends MessageParams {
private static final long MAX_UNCONSUMED_AUDIO_MS = 500;
final int mStreamType; final int mStreamType;
final int mSampleRateInHz; final int mSampleRateInHz;
final int mAudioFormat; final int mAudioFormat;
@@ -32,10 +35,16 @@ final class SynthesisMessageParams extends MessageParams {
final float mPan; final float mPan;
final EventLogger mLogger; final EventLogger mLogger;
final int mBytesPerFrame;
volatile AudioTrack mAudioTrack; volatile AudioTrack mAudioTrack;
// Not volatile, accessed only from the synthesis thread. // Written by the synthesis thread, but read on the audio playback
int mBytesWritten; // thread.
volatile int mBytesWritten;
// Not volatile, accessed only from the audio playback thread.
int mAudioBufferSize; int mAudioBufferSize;
// Always synchronized on "this".
int mUnconsumedBytes;
private final LinkedList<ListEntry> mDataBufferList = new LinkedList<ListEntry>(); private final LinkedList<ListEntry> mDataBufferList = new LinkedList<ListEntry>();
@@ -53,6 +62,8 @@ final class SynthesisMessageParams extends MessageParams {
mPan = pan; mPan = pan;
mLogger = logger; mLogger = logger;
mBytesPerFrame = getBytesPerFrame(mAudioFormat) * mChannelCount;
// initially null. // initially null.
mAudioTrack = null; mAudioTrack = null;
mBytesWritten = 0; mBytesWritten = 0;
@@ -64,18 +75,36 @@ final class SynthesisMessageParams extends MessageParams {
return TYPE_SYNTHESIS; return TYPE_SYNTHESIS;
} }
synchronized void addBuffer(byte[] buffer, int offset, int length) { synchronized void addBuffer(byte[] buffer) {
mDataBufferList.add(new ListEntry(buffer, offset, length)); long unconsumedAudioMs = 0;
while ((unconsumedAudioMs = getUnconsumedAudioLengthMs()) > MAX_UNCONSUMED_AUDIO_MS) {
try {
wait();
} catch (InterruptedException ie) {
return;
}
}
mDataBufferList.add(new ListEntry(buffer));
mUnconsumedBytes += buffer.length;
} }
synchronized void addBuffer(byte[] buffer) { synchronized void clearBuffers() {
mDataBufferList.add(new ListEntry(buffer, 0, buffer.length)); mDataBufferList.clear();
mUnconsumedBytes = 0;
notifyAll();
} }
synchronized ListEntry getNextBuffer() { synchronized ListEntry getNextBuffer() {
return mDataBufferList.poll(); ListEntry entry = mDataBufferList.poll();
} if (entry != null) {
mUnconsumedBytes -= entry.mBytes.length;
notifyAll();
}
return entry;
}
void setAudioTrack(AudioTrack audioTrack) { void setAudioTrack(AudioTrack audioTrack) {
mAudioTrack = audioTrack; mAudioTrack = audioTrack;
@@ -85,15 +114,29 @@ final class SynthesisMessageParams extends MessageParams {
return mAudioTrack; return mAudioTrack;
} }
// Must be called synchronized on this.
private long getUnconsumedAudioLengthMs() {
final int unconsumedFrames = mUnconsumedBytes / mBytesPerFrame;
final long estimatedTimeMs = unconsumedFrames * 1000 / mSampleRateInHz;
return estimatedTimeMs;
}
private static int getBytesPerFrame(int audioFormat) {
if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) {
return 1;
} else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
return 2;
}
return -1;
}
static final class ListEntry { static final class ListEntry {
final byte[] mBytes; final byte[] mBytes;
final int mOffset;
final int mLength;
ListEntry(byte[] bytes, int offset, int length) { ListEntry(byte[] bytes) {
mBytes = bytes; mBytes = bytes;
mOffset = offset;
mLength = length;
} }
} }
} }