diff --git a/api/current.txt b/api/current.txt index baadf5d865f2b..b4f193152f1c8 100644 --- a/api/current.txt +++ b/api/current.txt @@ -14987,6 +14987,7 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 d3be68b366213..da7932c0e3555 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -16199,6 +16199,7 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 8d3a9aab80e5f..8b2c26949c5d2 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -676,6 +676,63 @@ static jint android_media_AudioTrack_get_playback_rate(JNIEnv *env, jobject thi } +// ---------------------------------------------------------------------------- +static void android_media_AudioTrack_set_playback_settings(JNIEnv *env, jobject thiz, + jfloatArray floatArray, jintArray intArray) { + sp lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "AudioTrack not initialized"); + return; + } + + // NOTE: GetArrayRegion 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 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: SetArrayRegion 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) { @@ -942,6 +999,10 @@ static JNINativeMethod gMethods[] = { "(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 ded9d317df8e1..093ff26d0f862 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -946,12 +946,29 @@ public class AudioTrack } /** - * 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 @@ public class AudioTrack * 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} @@ -1322,6 +1340,42 @@ public class AudioTrack } + /** + * 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 AudioTrack is streaming + * (see {@link #MODE_STREAM}) and the + * buffer size is too small. For speeds greater than 1.0f, the AudioTrack 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 @@ -2207,6 +2261,15 @@ public class AudioTrack 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 0000000000000..ceb6bb14d4914 --- /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. + *

audio fallback mode: + * select out-of-range parameter handling. + *

    + *
  • {@link PlaybackSettings#AUDIO_FALLBACK_MODE_DEFAULT}: + * System will determine best handling.
  • + *
  • {@link PlaybackSettings#AUDIO_FALLBACK_MODE_MUTE}: + * Play silence for settings normally out of range.
  • + *
  • {@link PlaybackSettings#AUDIO_FALLBACK_MODE_FAIL}: + * Return {@link java.lang.IllegalArgumentException} from + * AudioTrack.setPlaybackSettings(PlaybackSettings).
  • + *
+ *

audio stretch mode: select + * timestretch handling. + *

    + *
  • {@link PlaybackSettings#AUDIO_STRETCH_MODE_DEFAULT}: + * System will determine best selection.
  • + *
  • {@link PlaybackSettings#AUDIO_STRETCH_MODE_VOICE}: + * Content is primarily voice.
  • + *
+ *

pitch: increases or decreases the tonal frequency of the audio content. + * It is expressed as a multiplicative factor, where normal pitch is 1.0f. + *

speed: 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. + *

Different combinations of speed and pitch may be used for audio playback; + * some common ones: + *

    + *
  • Pitch equals 1.0f. Speed change will be done with pitch preserved, + * often called timestretching.
  • + *
  • Pitch equals speed. Speed change will be done by resampling, + * similar to {@link AudioTrack#setPlaybackRate(int)}.
  • + *
+ */ +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 PlaybackSettings 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 PlaybackSettings 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 PlaybackSettings 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 PlaybackSettings 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 PlaybackSettings 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; + } +}