Add PlaybackSettings for use with AudioTrack
Change-Id: Ie59686d46869558d489a7600170ddace00e548d5
diff --git a/api/current.txt b/api/current.txt
index baadf5d..b4f1931 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14987,6 +14987,7 @@
method public int getPlayState();
method public int getPlaybackHeadPosition();
method public int getPlaybackRate();
+ method public android.media.PlaybackSettings getPlaybackSettings();
method public int getPositionNotificationPeriod();
method public android.media.AudioDeviceInfo getPreferredOutputDevice();
method public int getSampleRate();
@@ -15004,6 +15005,7 @@
method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener);
method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener, android.os.Handler);
method public int setPlaybackRate(int);
+ method public void setPlaybackSettings(android.media.PlaybackSettings);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredOutputDevice(android.media.AudioDeviceInfo);
method protected deprecated void setState(int);
@@ -16367,6 +16369,24 @@
method public abstract void onAudioDeviceConnection();
}
+ public final class PlaybackSettings {
+ ctor public PlaybackSettings();
+ method public android.media.PlaybackSettings allowDefaults();
+ method public int getAudioFallbackMode();
+ method public int getAudioStretchMode();
+ method public float getPitch();
+ method public float getSpeed();
+ method public android.media.PlaybackSettings setAudioFallbackMode(int);
+ method public android.media.PlaybackSettings setAudioStretchMode(int);
+ method public android.media.PlaybackSettings setPitch(float);
+ method public android.media.PlaybackSettings setSpeed(float);
+ field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
+ field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
+ field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
+ field public static final int AUDIO_STRETCH_MODE_DEFAULT = 0; // 0x0
+ field public static final int AUDIO_STRETCH_MODE_VOICE = 1; // 0x1
+ }
+
public final class Rating implements android.os.Parcelable {
method public int describeContents();
method public float getPercentRating();
diff --git a/api/system-current.txt b/api/system-current.txt
index d3be68b..da7932c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -16199,6 +16199,7 @@
method public int getPlayState();
method public int getPlaybackHeadPosition();
method public int getPlaybackRate();
+ method public android.media.PlaybackSettings getPlaybackSettings();
method public int getPositionNotificationPeriod();
method public android.media.AudioDeviceInfo getPreferredOutputDevice();
method public int getSampleRate();
@@ -16216,6 +16217,7 @@
method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener);
method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener, android.os.Handler);
method public int setPlaybackRate(int);
+ method public void setPlaybackSettings(android.media.PlaybackSettings);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredOutputDevice(android.media.AudioDeviceInfo);
method protected deprecated void setState(int);
@@ -17582,6 +17584,24 @@
method public abstract void onAudioDeviceConnection();
}
+ public final class PlaybackSettings {
+ ctor public PlaybackSettings();
+ method public android.media.PlaybackSettings allowDefaults();
+ method public int getAudioFallbackMode();
+ method public int getAudioStretchMode();
+ method public float getPitch();
+ method public float getSpeed();
+ method public android.media.PlaybackSettings setAudioFallbackMode(int);
+ method public android.media.PlaybackSettings setAudioStretchMode(int);
+ method public android.media.PlaybackSettings setPitch(float);
+ method public android.media.PlaybackSettings setSpeed(float);
+ field public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0; // 0x0
+ field public static final int AUDIO_FALLBACK_MODE_FAIL = 2; // 0x2
+ field public static final int AUDIO_FALLBACK_MODE_MUTE = 1; // 0x1
+ field public static final int AUDIO_STRETCH_MODE_DEFAULT = 0; // 0x0
+ field public static final int AUDIO_STRETCH_MODE_VOICE = 1; // 0x1
+ }
+
public final class Rating implements android.os.Parcelable {
method public int describeContents();
method public float getPercentRating();
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 8d3a9aa..8b2c269 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -677,6 +677,63 @@
// ----------------------------------------------------------------------------
+static void android_media_AudioTrack_set_playback_settings(JNIEnv *env, jobject thiz,
+ jfloatArray floatArray, jintArray intArray) {
+ sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+ if (lpTrack == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "AudioTrack not initialized");
+ return;
+ }
+
+ // NOTE: Get<Primitive>ArrayRegion throws ArrayIndexOutOfBoundsException if not valid.
+ // TODO: consider the actual occupancy.
+ float farray[2];
+ int iarray[2];
+ if ((env->GetFloatArrayRegion(floatArray, 0, 2, farray), env->ExceptionCheck()) == JNI_FALSE
+ &&
+ (env->GetIntArrayRegion(intArray, 0, 2, iarray), env->ExceptionCheck()) == JNI_FALSE) {
+ // arrays retrieved OK
+ AudioPlaybackRate playbackRate;
+ playbackRate.mSpeed = farray[0];
+ playbackRate.mPitch = farray[1];
+ playbackRate.mFallbackMode = (AudioTimestretchFallbackMode)iarray[0];
+ playbackRate.mStretchMode = (AudioTimestretchStretchMode)iarray[1];
+ if (lpTrack->setPlaybackRate(playbackRate) != OK) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "arguments out of range");
+ }
+ }
+}
+
+
+// ----------------------------------------------------------------------------
+static void android_media_AudioTrack_get_playback_settings(JNIEnv *env, jobject thiz,
+ jfloatArray floatArray, jintArray intArray) {
+ sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+ if (lpTrack == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "AudioTrack not initialized");
+ return;
+ }
+
+ AudioPlaybackRate playbackRate = lpTrack->getPlaybackRate();
+
+ float farray[2] = {
+ playbackRate.mSpeed,
+ playbackRate.mPitch,
+ };
+ int iarray[2] = {
+ playbackRate.mFallbackMode,
+ playbackRate.mStretchMode,
+ };
+ // NOTE: Set<Primitive>ArrayRegion throws ArrayIndexOutOfBoundsException if not valid.
+ env->SetFloatArrayRegion(floatArray, 0, 2, farray);
+ env->SetIntArrayRegion(intArray, 0, 2, iarray);
+}
+
+
+// ----------------------------------------------------------------------------
static jint android_media_AudioTrack_set_marker_pos(JNIEnv *env, jobject thiz,
jint markerPos) {
sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
@@ -942,6 +999,10 @@
"(I)I", (void *)android_media_AudioTrack_set_playback_rate},
{"native_get_playback_rate",
"()I", (void *)android_media_AudioTrack_get_playback_rate},
+ {"native_set_playback_settings",
+ "([F[I)V", (void *)android_media_AudioTrack_set_playback_settings},
+ {"native_get_playback_settings",
+ "([F[I)V", (void *)android_media_AudioTrack_get_playback_settings},
{"native_set_marker_pos","(I)I", (void *)android_media_AudioTrack_set_marker_pos},
{"native_get_marker_pos","()I", (void *)android_media_AudioTrack_get_marker_pos},
{"native_set_pos_update_period",
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index ded9d31..093ff26 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -946,13 +946,30 @@
}
/**
- * Returns the current playback rate in Hz.
+ * Returns the current playback sample rate rate in Hz.
*/
public int getPlaybackRate() {
return native_get_playback_rate();
}
/**
+ * Returns the current playback settings.
+ * See {@link #setPlaybackSettings(PlaybackSettings)} to set playback settings
+ * @return current {@link PlaybackSettings}.
+ * @throws IllegalStateException if track is not initialized.
+ */
+ public @NonNull PlaybackSettings getPlaybackSettings() {
+ float[] floatArray = new float[2];
+ int[] intArray = new int[2];
+ native_get_playback_settings(floatArray, intArray);
+ return new PlaybackSettings()
+ .setSpeed(floatArray[0])
+ .setPitch(floatArray[1])
+ .setAudioFallbackMode(intArray[0])
+ .setAudioStretchMode(intArray[1]);
+ }
+
+ /**
* Returns the configured audio data format. See {@link AudioFormat#ENCODING_PCM_16BIT}
* and {@link AudioFormat#ENCODING_PCM_8BIT}.
*/
@@ -1307,6 +1324,7 @@
* playback to last twice as long, but will also result in a pitch shift down by one octave.
* The valid sample rate range is from 1 Hz to twice the value returned by
* {@link #getNativeOutputSampleRate(int)}.
+ * Use {@link #setPlaybackSettings(PlaybackSettings)} for speed control.
* @param sampleRateInHz the sample rate expressed in Hz
* @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
* {@link #ERROR_INVALID_OPERATION}
@@ -1323,6 +1341,42 @@
/**
+ * Sets the playback settings.
+ * This method returns failure if it cannot apply the playback settings.
+ * One possible cause is that the parameters for speed or pitch are out of range.
+ * Another possible cause is that the <code>AudioTrack</code> is streaming
+ * (see {@link #MODE_STREAM}) and the
+ * buffer size is too small. For speeds greater than 1.0f, the <code>AudioTrack</code> buffer
+ * on configuration must be larger than the speed multiplied by the minimum size
+ * {@link #getMinBufferSize(int, int, int)}) to allow proper playback.
+ * @param settings see {@link PlaybackSettings}. In particular,
+ * speed, pitch, and audio mode should be set.
+ * @throws IllegalArgumentException if the settings are invalid or not accepted.
+ * @throws IllegalStateException if track is not initialized.
+ */
+ public void setPlaybackSettings(@NonNull PlaybackSettings settings) {
+ if (settings == null) {
+ throw new IllegalArgumentException("settings is null");
+ }
+ float[] floatArray;
+ int[] intArray;
+ try {
+ floatArray = new float[] {
+ settings.getSpeed(),
+ settings.getPitch(),
+ };
+ intArray = new int[] {
+ settings.getAudioFallbackMode(),
+ settings.getAudioStretchMode(),
+ };
+ } catch (IllegalStateException e) {
+ throw new IllegalArgumentException(e);
+ }
+ native_set_playback_settings(floatArray, intArray);
+ }
+
+
+ /**
* Sets the position of the notification marker. At most one marker can be active.
* @param markerInFrames marker position in wrapping frame units similar to
* {@link #getPlaybackHeadPosition}, or zero to disable the marker.
@@ -2207,6 +2261,15 @@
private native final int native_set_playback_rate(int sampleRateInHz);
private native final int native_get_playback_rate();
+ // floatArray must be a non-null array of length >= 2
+ // [0] is speed
+ // [1] is pitch
+ // intArray must be a non-null array of length >= 2
+ // [0] is audio fallback mode
+ // [1] is audio stretch mode
+ private native final void native_set_playback_settings(float[] floatArray, int[] intArray);
+ private native final void native_get_playback_settings(float[] floatArray, int[] intArray);
+
private native final int native_set_marker_pos(int marker);
private native final int native_get_marker_pos();
diff --git a/media/java/android/media/PlaybackSettings.java b/media/java/android/media/PlaybackSettings.java
new file mode 100644
index 0000000..ceb6bb1
--- /dev/null
+++ b/media/java/android/media/PlaybackSettings.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import android.annotation.IntDef;
+
+/**
+ * Structure for common playback settings.
+ *
+ * Used by {@link AudioTrack} {@link AudioTrack#getPlaybackSettings()} and
+ * {@link AudioTrack#setPlaybackSettings(PlaybackSettings)}
+ * to control playback behavior.
+ * <p> <strong>audio fallback mode:</strong>
+ * select out-of-range parameter handling.
+ * <ul>
+ * <li> {@link PlaybackSettings#AUDIO_FALLBACK_MODE_DEFAULT}:
+ * System will determine best handling. </li>
+ * <li> {@link PlaybackSettings#AUDIO_FALLBACK_MODE_MUTE}:
+ * Play silence for settings normally out of range.</li>
+ * <li> {@link PlaybackSettings#AUDIO_FALLBACK_MODE_FAIL}:
+ * Return {@link java.lang.IllegalArgumentException} from
+ * <code>AudioTrack.setPlaybackSettings(PlaybackSettings)</code>.</li>
+ * </ul>
+ * <p> <strong>audio stretch mode:</strong> select
+ * timestretch handling.
+ * <ul>
+ * <li> {@link PlaybackSettings#AUDIO_STRETCH_MODE_DEFAULT}:
+ * System will determine best selection. </li>
+ * <li> {@link PlaybackSettings#AUDIO_STRETCH_MODE_VOICE}:
+ * Content is primarily voice.</li>
+ * </ul>
+ * <p> <strong>pitch:</strong> increases or decreases the tonal frequency of the audio content.
+ * It is expressed as a multiplicative factor, where normal pitch is 1.0f.
+ * <p> <strong>speed:</strong> increases or decreases the time to
+ * play back a set of audio or video frames.
+ * It is expressed as a multiplicative factor, where normal speed is 1.0f.
+ * <p> Different combinations of speed and pitch may be used for audio playback;
+ * some common ones:
+ * <ul>
+ * <li> <em>Pitch equals 1.0f.</em> Speed change will be done with pitch preserved,
+ * often called <em>timestretching</em>.</li>
+ * <li> <em>Pitch equals speed.</em> Speed change will be done by <em>resampling</em>,
+ * similar to {@link AudioTrack#setPlaybackRate(int)}.</li>
+ * </ul>
+ */
+public final class PlaybackSettings {
+ /** @hide */
+ @IntDef(
+ value = {
+ AUDIO_FALLBACK_MODE_DEFAULT,
+ AUDIO_FALLBACK_MODE_MUTE,
+ AUDIO_FALLBACK_MODE_FAIL,
+ }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AudioFallbackMode {}
+ public static final int AUDIO_FALLBACK_MODE_DEFAULT = 0;
+ public static final int AUDIO_FALLBACK_MODE_MUTE = 1;
+ public static final int AUDIO_FALLBACK_MODE_FAIL = 2;
+
+ /** @hide */
+ @IntDef(
+ value = {
+ AUDIO_STRETCH_MODE_DEFAULT,
+ AUDIO_STRETCH_MODE_VOICE,
+ }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AudioStretchMode {}
+ public static final int AUDIO_STRETCH_MODE_DEFAULT = 0;
+ public static final int AUDIO_STRETCH_MODE_VOICE = 1;
+
+ // flags to indicate which settings are actually set
+ private static final int SET_SPEED = 1 << 0;
+ private static final int SET_PITCH = 1 << 1;
+ private static final int SET_AUDIO_FALLBACK_MODE = 1 << 2;
+ private static final int SET_AUDIO_STRETCH_MODE = 1 << 3;
+ private int mSet = 0;
+
+ // settings
+ private int mAudioFallbackMode = AUDIO_FALLBACK_MODE_DEFAULT;
+ private int mAudioStretchMode = AUDIO_STRETCH_MODE_DEFAULT;
+ private float mPitch = 1.0f;
+ private float mSpeed = 1.0f;
+
+ /**
+ * Allows defaults to be returned for properties not set.
+ * Otherwise a {@link java.lang.IllegalArgumentException} exception
+ * is raised when getting those properties
+ * which have defaults but have never been set.
+ * @return this <code>PlaybackSettings</code> instance.
+ */
+ public PlaybackSettings allowDefaults() {
+ mSet |= SET_AUDIO_FALLBACK_MODE | SET_AUDIO_STRETCH_MODE | SET_PITCH | SET_SPEED;
+ return this;
+ }
+
+ /**
+ * Sets the audio fallback mode.
+ * @param audioFallbackMode
+ * @return this <code>PlaybackSettings</code> instance.
+ */
+ public PlaybackSettings setAudioFallbackMode(@AudioFallbackMode int audioFallbackMode) {
+ mAudioFallbackMode = audioFallbackMode;
+ mSet |= SET_AUDIO_FALLBACK_MODE;
+ return this;
+ }
+
+ /**
+ * Retrieves the audio fallback mode.
+ * @return audio fallback mode
+ * @throws IllegalStateException if the audio fallback mode is not set.
+ */
+ public @AudioFallbackMode int getAudioFallbackMode() {
+ if ((mSet & SET_AUDIO_FALLBACK_MODE) == 0) {
+ throw new IllegalStateException("audio fallback mode not set");
+ }
+ return mAudioFallbackMode;
+ }
+
+ /**
+ * Sets the audio stretch mode.
+ * @param audioStretchMode
+ * @return this <code>PlaybackSettings</code> instance.
+ */
+ public PlaybackSettings setAudioStretchMode(@AudioStretchMode int audioStretchMode) {
+ mAudioStretchMode = audioStretchMode;
+ mSet |= SET_AUDIO_STRETCH_MODE;
+ return this;
+ }
+
+ /**
+ * Retrieves the audio stretch mode.
+ * @return audio stretch mode
+ * @throws IllegalStateException if the audio stretch mode is not set.
+ */
+ public @AudioStretchMode int getAudioStretchMode() {
+ if ((mSet & SET_AUDIO_STRETCH_MODE) == 0) {
+ throw new IllegalStateException("audio stretch mode not set");
+ }
+ return mAudioStretchMode;
+ }
+
+ /**
+ * Sets the pitch factor.
+ * @param pitch
+ * @return this <code>PlaybackSettings</code> instance.
+ */
+ public PlaybackSettings setPitch(float pitch) {
+ mPitch = pitch;
+ mSet |= SET_PITCH;
+ return this;
+ }
+
+ /**
+ * Retrieves the pitch factor.
+ * @return pitch
+ * @throws IllegalStateException if pitch is not set.
+ */
+ public float getPitch() {
+ if ((mSet & SET_PITCH) == 0) {
+ throw new IllegalStateException("pitch not set");
+ }
+ return mPitch;
+ }
+
+ /**
+ * Sets the speed factor.
+ * @param speed
+ * @return this <code>PlaybackSettings</code> instance.
+ */
+ public PlaybackSettings setSpeed(float speed) {
+ mSpeed = speed;
+ mSet |= SET_SPEED;
+ return this;
+ }
+
+ /**
+ * Retrieves the speed factor.
+ * @return speed
+ * @throws IllegalStateException if speed is not set.
+ */
+ public float getSpeed() {
+ if ((mSet & SET_SPEED) == 0) {
+ throw new IllegalStateException("speed not set");
+ }
+ return mSpeed;
+ }
+}