Add support for audio session id in the TTS
+ #playEarcon & #queueAudio respects request Volume/Pan settings. Bug:15432115 Change-Id: I136afef77afbc56c34810c64123f7be4b431d378
This commit is contained in:
@@ -16,9 +16,12 @@
|
||||
package android.speech.tts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioSystem;
|
||||
import android.media.AudioTrack;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.ConditionVariable;
|
||||
import android.speech.tts.TextToSpeechService.AudioOutputParams;
|
||||
import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -27,7 +30,7 @@ class AudioPlaybackQueueItem extends PlaybackQueueItem {
|
||||
|
||||
private final Context mContext;
|
||||
private final Uri mUri;
|
||||
private final int mStreamType;
|
||||
private final AudioOutputParams mAudioParams;
|
||||
|
||||
private final ConditionVariable mDone;
|
||||
private MediaPlayer mPlayer;
|
||||
@@ -35,12 +38,12 @@ class AudioPlaybackQueueItem extends PlaybackQueueItem {
|
||||
|
||||
AudioPlaybackQueueItem(UtteranceProgressDispatcher dispatcher,
|
||||
Object callerIdentity,
|
||||
Context context, Uri uri, int streamType) {
|
||||
Context context, Uri uri, AudioOutputParams audioParams) {
|
||||
super(dispatcher, callerIdentity);
|
||||
|
||||
mContext = context;
|
||||
mUri = uri;
|
||||
mStreamType = streamType;
|
||||
mAudioParams = audioParams;
|
||||
|
||||
mDone = new ConditionVariable();
|
||||
mPlayer = null;
|
||||
@@ -73,7 +76,11 @@ class AudioPlaybackQueueItem extends PlaybackQueueItem {
|
||||
mDone.open();
|
||||
}
|
||||
});
|
||||
mPlayer.setAudioStreamType(mStreamType);
|
||||
mPlayer.setAudioStreamType(mAudioParams.mStreamType);
|
||||
setupVolume(mPlayer, mAudioParams.mVolume, mAudioParams.mPan);
|
||||
if (mAudioParams.mSessionId != AudioSystem.AUDIO_SESSION_ALLOCATE) {
|
||||
mPlayer.setAudioSessionId(mAudioParams.mSessionId);
|
||||
}
|
||||
mPlayer.start();
|
||||
mDone.block();
|
||||
finish();
|
||||
@@ -89,6 +96,23 @@ class AudioPlaybackQueueItem extends PlaybackQueueItem {
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupVolume(MediaPlayer player, float volume, float pan) {
|
||||
final float vol = clip(volume, 0.0f, 1.0f);
|
||||
final float panning = clip(pan, -1.0f, 1.0f);
|
||||
|
||||
float volLeft = vol, volRight = vol;
|
||||
if (panning > 0.0f) {
|
||||
volLeft *= (1.0f - panning);
|
||||
} else if (panning < 0.0f) {
|
||||
volRight *= (1.0f + panning);
|
||||
}
|
||||
player.setVolume(volLeft, volRight);
|
||||
}
|
||||
|
||||
private static final float clip(float value, float min, float max) {
|
||||
return value < min ? min : (value < max ? value : max);
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
try {
|
||||
mPlayer.stop();
|
||||
|
||||
@@ -4,6 +4,7 @@ package android.speech.tts;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioTrack;
|
||||
import android.speech.tts.TextToSpeechService.AudioOutputParams;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
@@ -44,12 +45,11 @@ class BlockingAudioTrack {
|
||||
private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
|
||||
|
||||
|
||||
private final int mStreamType;
|
||||
private final AudioOutputParams mAudioParams;
|
||||
private final int mSampleRateInHz;
|
||||
private final int mAudioFormat;
|
||||
private final int mChannelCount;
|
||||
private final float mVolume;
|
||||
private final float mPan;
|
||||
|
||||
|
||||
private final int mBytesPerFrame;
|
||||
/**
|
||||
@@ -73,15 +73,14 @@ class BlockingAudioTrack {
|
||||
private AudioTrack mAudioTrack;
|
||||
private volatile boolean mStopped;
|
||||
|
||||
BlockingAudioTrack(int streamType, int sampleRate,
|
||||
int audioFormat, int channelCount,
|
||||
float volume, float pan) {
|
||||
mStreamType = streamType;
|
||||
private int mSessionId;
|
||||
|
||||
BlockingAudioTrack(AudioOutputParams audioParams, int sampleRate,
|
||||
int audioFormat, int channelCount) {
|
||||
mAudioParams = audioParams;
|
||||
mSampleRateInHz = sampleRate;
|
||||
mAudioFormat = audioFormat;
|
||||
mChannelCount = channelCount;
|
||||
mVolume = volume;
|
||||
mPan = pan;
|
||||
|
||||
mBytesPerFrame = AudioFormat.getBytesPerSample(mAudioFormat) * mChannelCount;
|
||||
mIsShortUtterance = false;
|
||||
@@ -215,8 +214,9 @@ class BlockingAudioTrack {
|
||||
= AudioTrack.getMinBufferSize(mSampleRateInHz, channelConfig, mAudioFormat);
|
||||
int bufferSizeInBytes = Math.max(MIN_AUDIO_BUFFER_SIZE, minBufferSizeInBytes);
|
||||
|
||||
AudioTrack audioTrack = new AudioTrack(mStreamType, mSampleRateInHz, channelConfig,
|
||||
mAudioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM);
|
||||
AudioTrack audioTrack = new AudioTrack(mAudioParams.mStreamType, mSampleRateInHz,
|
||||
channelConfig, mAudioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM,
|
||||
mAudioParams.mSessionId);
|
||||
if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
||||
Log.w(TAG, "Unable to create audio track.");
|
||||
audioTrack.release();
|
||||
@@ -225,7 +225,7 @@ class BlockingAudioTrack {
|
||||
|
||||
mAudioBufferSize = bufferSizeInBytes;
|
||||
|
||||
setupVolume(audioTrack, mVolume, mPan);
|
||||
setupVolume(audioTrack, mAudioParams.mVolume, mAudioParams.mPan);
|
||||
return audioTrack;
|
||||
}
|
||||
|
||||
@@ -328,19 +328,11 @@ class BlockingAudioTrack {
|
||||
}
|
||||
|
||||
private static final long clip(long value, long min, long max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
|
||||
return value;
|
||||
return value < min ? min : (value < max ? value : max);
|
||||
}
|
||||
|
||||
private static float clip(float value, float min, float max) {
|
||||
return value > max ? max : (value < min ? min : value);
|
||||
private static final float clip(float value, float min, float max) {
|
||||
return value < min ? min : (value < max ? value : max);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package android.speech.tts;
|
||||
|
||||
import android.speech.tts.TextToSpeechService.AudioOutputParams;
|
||||
import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -28,23 +29,7 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
|
||||
|
||||
private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
|
||||
|
||||
/**
|
||||
* Audio stream type. Must be one of the STREAM_ contants defined in
|
||||
* {@link android.media.AudioManager}.
|
||||
*/
|
||||
private final int mStreamType;
|
||||
|
||||
/**
|
||||
* Volume, in the range [0.0f, 1.0f]. The default value is
|
||||
* {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
|
||||
*/
|
||||
private final float mVolume;
|
||||
|
||||
/**
|
||||
* Left/right position of the audio, in the range [-1.0f, 1.0f].
|
||||
* The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
|
||||
*/
|
||||
private final float mPan;
|
||||
private final AudioOutputParams mAudioParams;
|
||||
|
||||
/**
|
||||
* Guards {@link #mAudioTrackHandler}, {@link #mItem} and {@link #mStopped}.
|
||||
@@ -65,13 +50,11 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
|
||||
private final Object mCallerIdentity;
|
||||
private final AbstractEventLogger mLogger;
|
||||
|
||||
PlaybackSynthesisCallback(int streamType, float volume, float pan,
|
||||
AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher,
|
||||
Object callerIdentity, AbstractEventLogger logger, boolean clientIsUsingV2) {
|
||||
PlaybackSynthesisCallback(AudioOutputParams audioParams, AudioPlaybackHandler audioTrackHandler,
|
||||
UtteranceProgressDispatcher dispatcher, Object callerIdentity,
|
||||
AbstractEventLogger logger, boolean clientIsUsingV2) {
|
||||
super(clientIsUsingV2);
|
||||
mStreamType = streamType;
|
||||
mVolume = volume;
|
||||
mPan = pan;
|
||||
mAudioParams = audioParams;
|
||||
mAudioTrackHandler = audioTrackHandler;
|
||||
mDispatcher = dispatcher;
|
||||
mCallerIdentity = callerIdentity;
|
||||
@@ -161,7 +144,7 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
|
||||
return TextToSpeech.ERROR;
|
||||
}
|
||||
SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem(
|
||||
mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
|
||||
mAudioParams, sampleRateInHz, audioFormat, channelCount,
|
||||
mDispatcher, mCallerIdentity, mLogger);
|
||||
mAudioTrackHandler.enqueue(item);
|
||||
mItem = item;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package android.speech.tts;
|
||||
|
||||
import android.speech.tts.TextToSpeechService.AudioOutputParams;
|
||||
import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -62,9 +63,8 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
|
||||
private final BlockingAudioTrack mAudioTrack;
|
||||
private final AbstractEventLogger mLogger;
|
||||
|
||||
SynthesisPlaybackQueueItem(int streamType, int sampleRate,
|
||||
int audioFormat, int channelCount,
|
||||
float volume, float pan, UtteranceProgressDispatcher dispatcher,
|
||||
SynthesisPlaybackQueueItem(AudioOutputParams audioParams, int sampleRate,
|
||||
int audioFormat, int channelCount, UtteranceProgressDispatcher dispatcher,
|
||||
Object callerIdentity, AbstractEventLogger logger) {
|
||||
super(dispatcher, callerIdentity);
|
||||
|
||||
@@ -74,8 +74,7 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
|
||||
mDone = false;
|
||||
mStatusCode = TextToSpeech.SUCCESS;
|
||||
|
||||
mAudioTrack = new BlockingAudioTrack(streamType, sampleRate, audioFormat,
|
||||
channelCount, volume, pan);
|
||||
mAudioTrack = new BlockingAudioTrack(audioParams, sampleRate, audioFormat, channelCount);
|
||||
mLogger = logger;
|
||||
}
|
||||
|
||||
|
||||
@@ -564,6 +564,17 @@ public class TextToSpeech {
|
||||
* @see TextToSpeech#getFeatures(java.util.Locale)
|
||||
*/
|
||||
public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
|
||||
|
||||
/**
|
||||
* Parameter key to specify an audio session identifier (obtained from
|
||||
* {@link AudioManager#allocateAudioSessionId()}) that will be used by the request audio
|
||||
* output. It can be used to associate one of the {@link android.media.audiofx.AudioEffect}
|
||||
* objects with the synthesis (or earcon) output.
|
||||
*
|
||||
* @see TextToSpeech#speak(String, int, HashMap)
|
||||
* @see TextToSpeech#playEarcon(String, int, HashMap)
|
||||
*/
|
||||
public static final String KEY_PARAM_SESSION_ID = "sessionId";
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
@@ -1336,6 +1347,7 @@ public class TextToSpeech {
|
||||
if (params != null && !params.isEmpty()) {
|
||||
Bundle bundle = new Bundle(mParams);
|
||||
copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
|
||||
copyIntParam(bundle, params, Engine.KEY_PARAM_SESSION_ID);
|
||||
copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
|
||||
copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
|
||||
copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
|
||||
|
||||
@@ -17,6 +17,8 @@ package android.speech.tts;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioSystem;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
@@ -419,6 +421,73 @@ public abstract class TextToSpeechService extends Service {
|
||||
public void dispatchOnError(int errorCode);
|
||||
}
|
||||
|
||||
|
||||
/** Set of parameters affecting audio output. */
|
||||
static class AudioOutputParams {
|
||||
/**
|
||||
* Audio session identifier. May be used to associate audio playback with one of the
|
||||
* {@link android.media.audiofx.AudioEffect} objects. If not specified by client,
|
||||
* it should be equal to {@link AudioSystem#AUDIO_SESSION_ALLOCATE}.
|
||||
*/
|
||||
public final int mSessionId;
|
||||
|
||||
/**
|
||||
* Audio stream type. Must be one of the STREAM_ contants defined in
|
||||
* {@link android.media.AudioManager}.
|
||||
*/
|
||||
public final int mStreamType;
|
||||
|
||||
/**
|
||||
* Volume, in the range [0.0f, 1.0f]. The default value is
|
||||
* {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
|
||||
*/
|
||||
public final float mVolume;
|
||||
|
||||
/**
|
||||
* Left/right position of the audio, in the range [-1.0f, 1.0f].
|
||||
* The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
|
||||
*/
|
||||
public final float mPan;
|
||||
|
||||
/** Create AudioOutputParams with default values */
|
||||
AudioOutputParams() {
|
||||
mSessionId = AudioSystem.AUDIO_SESSION_ALLOCATE;
|
||||
mStreamType = Engine.DEFAULT_STREAM;
|
||||
mVolume = Engine.DEFAULT_VOLUME;
|
||||
mPan = Engine.DEFAULT_PAN;
|
||||
}
|
||||
|
||||
AudioOutputParams(int sessionId, int streamType, float volume, float pan) {
|
||||
mSessionId = sessionId;
|
||||
mStreamType = streamType;
|
||||
mVolume = volume;
|
||||
mPan = pan;
|
||||
}
|
||||
|
||||
/** Create AudioOutputParams from A {@link SynthesisRequest#getParams()} bundle */
|
||||
static AudioOutputParams createFromV1ParamsBundle(Bundle paramsBundle) {
|
||||
if (paramsBundle == null) {
|
||||
return new AudioOutputParams();
|
||||
}
|
||||
|
||||
return new AudioOutputParams(
|
||||
paramsBundle.getInt(
|
||||
Engine.KEY_PARAM_SESSION_ID,
|
||||
AudioSystem.AUDIO_SESSION_ALLOCATE),
|
||||
paramsBundle.getInt(
|
||||
Engine.KEY_PARAM_STREAM,
|
||||
Engine.DEFAULT_STREAM),
|
||||
paramsBundle.getFloat(
|
||||
Engine.KEY_PARAM_VOLUME,
|
||||
Engine.DEFAULT_VOLUME),
|
||||
paramsBundle.getFloat(
|
||||
Engine.KEY_PARAM_PAN,
|
||||
Engine.DEFAULT_PAN));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An item in the synth thread queue.
|
||||
*/
|
||||
@@ -592,16 +661,8 @@ public abstract class TextToSpeechService extends Service {
|
||||
return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null);
|
||||
}
|
||||
|
||||
int getStreamType() {
|
||||
return getIntParam(mParams, Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
|
||||
}
|
||||
|
||||
float getVolume() {
|
||||
return getFloatParam(mParams, Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
|
||||
}
|
||||
|
||||
float getPan() {
|
||||
return getFloatParam(mParams, Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
|
||||
AudioOutputParams getAudioParams() {
|
||||
return AudioOutputParams.createFromV1ParamsBundle(mParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +729,7 @@ public abstract class TextToSpeechService extends Service {
|
||||
}
|
||||
|
||||
protected AbstractSynthesisCallback createSynthesisCallback() {
|
||||
return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
|
||||
return new PlaybackSynthesisCallback(getAudioParams(),
|
||||
mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false);
|
||||
}
|
||||
|
||||
@@ -743,7 +804,7 @@ public abstract class TextToSpeechService extends Service {
|
||||
Bundle params, Uri uri) {
|
||||
super(callerIdentity, callerUid, callerPid, params);
|
||||
mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
|
||||
TextToSpeechService.this, uri, getStreamType());
|
||||
TextToSpeechService.this, uri, getAudioParams());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user