package android.speech.tts;
import android.annotation.NonNull;
import android.media.AudioFormat;
import android.speech.tts.TextToSpeechService.AudioOutputParams;
import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
import android.util.Log;
class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
private static final String TAG = "PlaybackSynthesisRequest";
private static final boolean DBG = false;
private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
private final AudioOutputParams mAudioParams;
private final Object mStateLock = new Object();
private final AudioPlaybackHandler mAudioTrackHandler;
private SynthesisPlaybackQueueItem mItem = null;
private volatile boolean mDone = false;
protected int mStatusCode;
private final UtteranceProgressDispatcher mDispatcher;
private final Object mCallerIdentity;
private final AbstractEventLogger mLogger;
PlaybackSynthesisCallback(@NonNull AudioOutputParams audioParams,
@NonNull AudioPlaybackHandler audioTrackHandler,
@NonNull UtteranceProgressDispatcher dispatcher, @NonNull Object callerIdentity,
@NonNull AbstractEventLogger logger, boolean clientIsUsingV2) {
super(clientIsUsingV2);
mAudioParams = audioParams;
mAudioTrackHandler = audioTrackHandler;
mDispatcher = dispatcher;
mCallerIdentity = callerIdentity;
mLogger = logger;
mStatusCode = TextToSpeech.SUCCESS;
}
@Override
void stop() {
if (DBG) Log.d(TAG, "stop()");
SynthesisPlaybackQueueItem item;
synchronized (mStateLock) {
if (mDone) {
return;
}
if (mStatusCode == TextToSpeech.STOPPED) {
Log.w(TAG, "stop() called twice");
return;
}
item = mItem;
mStatusCode = TextToSpeech.STOPPED;
}
if (item != null) {
item.stop(TextToSpeech.STOPPED);
} else {
mLogger.onCompleted(TextToSpeech.STOPPED);
mDispatcher.dispatchOnStop();
}
}
@Override
public int getMaxBufferSize() {
return MIN_AUDIO_BUFFER_SIZE;
}
@Override
public boolean hasStarted() {
synchronized (mStateLock) {
return mItem != null;
}
}
@Override
public boolean hasFinished() {
synchronized (mStateLock) {
return mDone;
}
}
@Override
public int start(int sampleRateInHz, int audioFormat, int channelCount) {
if (DBG) Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat + "," + channelCount
+ ")");
if (audioFormat != AudioFormat.ENCODING_PCM_8BIT &&
audioFormat != AudioFormat.ENCODING_PCM_16BIT &&
audioFormat != AudioFormat.ENCODING_PCM_FLOAT) {
Log.w(TAG, "Audio format encoding " + audioFormat + " not supported. Please use one " +
"of AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT or " +
"AudioFormat.ENCODING_PCM_FLOAT");
}
mDispatcher.dispatchOnBeginSynthesis(sampleRateInHz, audioFormat, channelCount);
int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount);
synchronized (mStateLock) {
if (channelConfig == 0) {
Log.e(TAG, "Unsupported number of channels :" + channelCount);
mStatusCode = TextToSpeech.ERROR_OUTPUT;
return TextToSpeech.ERROR;
}
if (mStatusCode == TextToSpeech.STOPPED) {
if (DBG) Log.d(TAG, "stop() called before start(), returning.");
return errorCodeOnStop();
}
if (mStatusCode != TextToSpeech.SUCCESS) {
if (DBG) Log.d(TAG, "Error was raised");
return TextToSpeech.ERROR;
}
if (mItem != null) {
Log.e(TAG, "Start called twice");
return TextToSpeech.ERROR;
}
SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem(
mAudioParams, sampleRateInHz, audioFormat, channelCount,
mDispatcher, mCallerIdentity, mLogger);
mAudioTrackHandler.enqueue(item);
mItem = item;
}
return TextToSpeech.SUCCESS;
}
@Override
public int audioAvailable(byte[] buffer, int offset, int length) {
if (DBG) Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length
+ ")");
if (length > getMaxBufferSize() || length <= 0) {
throw new IllegalArgumentException("buffer is too large or of zero length (" +
+ length + " bytes)");
}
SynthesisPlaybackQueueItem item = null;
synchronized (mStateLock) {
if (mItem == null) {
mStatusCode = TextToSpeech.ERROR_OUTPUT;
return TextToSpeech.ERROR;
}
if (mStatusCode != TextToSpeech.SUCCESS) {
if (DBG) Log.d(TAG, "Error was raised");
return TextToSpeech.ERROR;
}
if (mStatusCode == TextToSpeech.STOPPED) {
return errorCodeOnStop();
}
item = mItem;
}
final byte[] bufferCopy = new byte[length];
System.arraycopy(buffer, offset, bufferCopy, 0, length);
mDispatcher.dispatchOnAudioAvailable(bufferCopy);
try {
item.put(bufferCopy);
} catch (InterruptedException ie) {
synchronized (mStateLock) {
mStatusCode = TextToSpeech.ERROR_OUTPUT;
return TextToSpeech.ERROR;
}
}
mLogger.onEngineDataReceived();
return TextToSpeech.SUCCESS;
}
@Override
public int done() {
if (DBG) Log.d(TAG, "done()");
int statusCode = 0;
SynthesisPlaybackQueueItem item = null;
synchronized (mStateLock) {
if (mDone) {
Log.w(TAG, "Duplicate call to done()");
return TextToSpeech.ERROR;
}
if (mStatusCode == TextToSpeech.STOPPED) {
if (DBG) Log.d(TAG, "Request has been aborted.");
return errorCodeOnStop();
}
mDone = true;
if (mItem == null) {
Log.w(TAG, "done() was called before start() call");
if (mStatusCode == TextToSpeech.SUCCESS) {
mDispatcher.dispatchOnSuccess();
} else {
mDispatcher.dispatchOnError(mStatusCode);
}
mLogger.onEngineComplete();
return TextToSpeech.ERROR;
}
item = mItem;
statusCode = mStatusCode;
}
if (statusCode == TextToSpeech.SUCCESS) {
item.done();
} else {
item.stop(statusCode);
}
mLogger.onEngineComplete();
return TextToSpeech.SUCCESS;
}
@Override
public void error() {
error(TextToSpeech.ERROR_SYNTHESIS);
}
@Override
public void error(int errorCode) {
if (DBG) Log.d(TAG, "error() [will call stop]");
synchronized (mStateLock) {
if (mDone) {
return;
}
mStatusCode = errorCode;
}
}
@Override
public void rangeStart(int markerInFrames, int start, int end) {
if (mItem == null) {
Log.e(TAG, "mItem is null");
return;
}
mItem.rangeStart(markerInFrames, start, end);
}
}