Merge "Don't allow TTS engines to synthesize too for ahead."
This commit is contained in:
committed by
Android (Google) Code Review
commit
5cdc720dca
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user