diff --git a/api/current.txt b/api/current.txt index 0a1f22564822b..d53a4b8577ea1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20999,13 +20999,14 @@ package android.media { field public long nanoTime; } - public class AudioTrack implements android.media.AudioRouting { + public class AudioTrack implements android.media.AudioRouting android.media.VolumeAutomation { ctor public deprecated AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException; ctor public deprecated AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException; ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException; method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler); method public int attachAuxEffect(int); + method public android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration); method public void flush(); method public int getAudioFormat(); method public int getAudioSessionId(); @@ -22346,7 +22347,7 @@ package android.media { field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1 } - public class MediaPlayer { + public class MediaPlayer implements android.media.VolumeAutomation { ctor public MediaPlayer(); method public void addTimedTextSource(java.lang.String, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void addTimedTextSource(android.content.Context, android.net.Uri, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; @@ -22358,6 +22359,7 @@ package android.media { method public static android.media.MediaPlayer create(android.content.Context, android.net.Uri, android.view.SurfaceHolder, android.media.AudioAttributes, int); method public static android.media.MediaPlayer create(android.content.Context, int); method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int); + method public android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration); method public void deselectTrack(int) throws java.lang.IllegalStateException; method public int getAudioSessionId(); method public android.media.BufferingParams getBufferingParams(); @@ -23185,6 +23187,10 @@ package android.media { ctor public UnsupportedSchemeException(java.lang.String); } + public abstract interface VolumeAutomation { + method public abstract android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration); + } + public abstract class VolumeProvider { ctor public VolumeProvider(int, int, int); method public final int getCurrentVolume(); @@ -23198,6 +23204,53 @@ package android.media { field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1 } + public final class VolumeShaper implements java.lang.AutoCloseable { + method public void apply(android.media.VolumeShaper.Operation); + method public void close(); + method public float getVolume(); + method public void replace(android.media.VolumeShaper.Configuration, android.media.VolumeShaper.Operation, boolean); + } + + public static final class VolumeShaper.Configuration implements android.os.Parcelable { + method public int describeContents(); + method public double getDurationMs(); + method public int getInterpolatorType(); + method public static int getMaximumCurvePoints(); + method public float[] getTimes(); + method public float[] getVolumes(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final android.media.VolumeShaper.Configuration CUBIC_RAMP; + field public static final int INTERPOLATOR_TYPE_CUBIC = 2; // 0x2 + field public static final int INTERPOLATOR_TYPE_CUBIC_MONOTONIC = 3; // 0x3 + field public static final int INTERPOLATOR_TYPE_LINEAR = 1; // 0x1 + field public static final int INTERPOLATOR_TYPE_STEP = 0; // 0x0 + field public static final android.media.VolumeShaper.Configuration LINEAR_RAMP; + field public static final android.media.VolumeShaper.Configuration SCURVE_RAMP; + field public static final android.media.VolumeShaper.Configuration SINE_RAMP; + } + + public static final class VolumeShaper.Configuration.Builder { + ctor public VolumeShaper.Configuration.Builder(); + ctor public VolumeShaper.Configuration.Builder(android.media.VolumeShaper.Configuration); + method public android.media.VolumeShaper.Configuration build(); + method public android.media.VolumeShaper.Configuration.Builder invertVolumes(); + method public android.media.VolumeShaper.Configuration.Builder reflectTimes(); + method public android.media.VolumeShaper.Configuration.Builder scaleToEndVolume(float); + method public android.media.VolumeShaper.Configuration.Builder scaleToStartVolume(float); + method public android.media.VolumeShaper.Configuration.Builder setCurve(float[], float[]); + method public android.media.VolumeShaper.Configuration.Builder setDurationMs(double); + method public android.media.VolumeShaper.Configuration.Builder setInterpolatorType(int); + } + + public static final class VolumeShaper.Operation implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final android.media.VolumeShaper.Operation PLAY; + field public static final android.media.VolumeShaper.Operation REVERSE; + } + } package android.media.audiofx { diff --git a/api/system-current.txt b/api/system-current.txt index 919efdd39854a..e5de7dcd2b6e4 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -22687,13 +22687,14 @@ package android.media { field public long nanoTime; } - public class AudioTrack implements android.media.AudioRouting { + public class AudioTrack implements android.media.AudioRouting android.media.VolumeAutomation { ctor public deprecated AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException; ctor public deprecated AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException; ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException; method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler); method public int attachAuxEffect(int); + method public android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration); method public void flush(); method public int getAudioFormat(); method public int getAudioSessionId(); @@ -24034,7 +24035,7 @@ package android.media { field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1 } - public class MediaPlayer { + public class MediaPlayer implements android.media.VolumeAutomation { ctor public MediaPlayer(); method public void addTimedTextSource(java.lang.String, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void addTimedTextSource(android.content.Context, android.net.Uri, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; @@ -24046,6 +24047,7 @@ package android.media { method public static android.media.MediaPlayer create(android.content.Context, android.net.Uri, android.view.SurfaceHolder, android.media.AudioAttributes, int); method public static android.media.MediaPlayer create(android.content.Context, int); method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int); + method public android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration); method public void deselectTrack(int) throws java.lang.IllegalStateException; method public int getAudioSessionId(); method public android.media.BufferingParams getBufferingParams(); @@ -24884,6 +24886,10 @@ package android.media { ctor public UnsupportedSchemeException(java.lang.String); } + public abstract interface VolumeAutomation { + method public abstract android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration); + } + public abstract class VolumeProvider { ctor public VolumeProvider(int, int, int); method public final int getCurrentVolume(); @@ -24897,6 +24903,53 @@ package android.media { field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1 } + public final class VolumeShaper implements java.lang.AutoCloseable { + method public void apply(android.media.VolumeShaper.Operation); + method public void close(); + method public float getVolume(); + method public void replace(android.media.VolumeShaper.Configuration, android.media.VolumeShaper.Operation, boolean); + } + + public static final class VolumeShaper.Configuration implements android.os.Parcelable { + method public int describeContents(); + method public double getDurationMs(); + method public int getInterpolatorType(); + method public static int getMaximumCurvePoints(); + method public float[] getTimes(); + method public float[] getVolumes(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final android.media.VolumeShaper.Configuration CUBIC_RAMP; + field public static final int INTERPOLATOR_TYPE_CUBIC = 2; // 0x2 + field public static final int INTERPOLATOR_TYPE_CUBIC_MONOTONIC = 3; // 0x3 + field public static final int INTERPOLATOR_TYPE_LINEAR = 1; // 0x1 + field public static final int INTERPOLATOR_TYPE_STEP = 0; // 0x0 + field public static final android.media.VolumeShaper.Configuration LINEAR_RAMP; + field public static final android.media.VolumeShaper.Configuration SCURVE_RAMP; + field public static final android.media.VolumeShaper.Configuration SINE_RAMP; + } + + public static final class VolumeShaper.Configuration.Builder { + ctor public VolumeShaper.Configuration.Builder(); + ctor public VolumeShaper.Configuration.Builder(android.media.VolumeShaper.Configuration); + method public android.media.VolumeShaper.Configuration build(); + method public android.media.VolumeShaper.Configuration.Builder invertVolumes(); + method public android.media.VolumeShaper.Configuration.Builder reflectTimes(); + method public android.media.VolumeShaper.Configuration.Builder scaleToEndVolume(float); + method public android.media.VolumeShaper.Configuration.Builder scaleToStartVolume(float); + method public android.media.VolumeShaper.Configuration.Builder setCurve(float[], float[]); + method public android.media.VolumeShaper.Configuration.Builder setDurationMs(double); + method public android.media.VolumeShaper.Configuration.Builder setInterpolatorType(int); + } + + public static final class VolumeShaper.Operation implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final android.media.VolumeShaper.Operation PLAY; + field public static final android.media.VolumeShaper.Operation REVERSE; + } + } package android.media.audiofx { diff --git a/api/test-current.txt b/api/test-current.txt index 7a7b93e8e1df5..cff6ffec6ae61 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -21095,13 +21095,14 @@ package android.media { field public long nanoTime; } - public class AudioTrack implements android.media.AudioRouting { + public class AudioTrack implements android.media.AudioRouting android.media.VolumeAutomation { ctor public deprecated AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException; ctor public deprecated AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException; ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException; method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler); method public int attachAuxEffect(int); + method public android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration); method public void flush(); method public int getAudioFormat(); method public int getAudioSessionId(); @@ -22442,7 +22443,7 @@ package android.media { field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1 } - public class MediaPlayer { + public class MediaPlayer implements android.media.VolumeAutomation { ctor public MediaPlayer(); method public void addTimedTextSource(java.lang.String, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void addTimedTextSource(android.content.Context, android.net.Uri, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; @@ -22454,6 +22455,7 @@ package android.media { method public static android.media.MediaPlayer create(android.content.Context, android.net.Uri, android.view.SurfaceHolder, android.media.AudioAttributes, int); method public static android.media.MediaPlayer create(android.content.Context, int); method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int); + method public android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration); method public void deselectTrack(int) throws java.lang.IllegalStateException; method public int getAudioSessionId(); method public android.media.BufferingParams getBufferingParams(); @@ -23281,6 +23283,10 @@ package android.media { ctor public UnsupportedSchemeException(java.lang.String); } + public abstract interface VolumeAutomation { + method public abstract android.media.VolumeShaper createVolumeShaper(android.media.VolumeShaper.Configuration); + } + public abstract class VolumeProvider { ctor public VolumeProvider(int, int, int); method public final int getCurrentVolume(); @@ -23294,6 +23300,53 @@ package android.media { field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1 } + public final class VolumeShaper implements java.lang.AutoCloseable { + method public void apply(android.media.VolumeShaper.Operation); + method public void close(); + method public float getVolume(); + method public void replace(android.media.VolumeShaper.Configuration, android.media.VolumeShaper.Operation, boolean); + } + + public static final class VolumeShaper.Configuration implements android.os.Parcelable { + method public int describeContents(); + method public double getDurationMs(); + method public int getInterpolatorType(); + method public static int getMaximumCurvePoints(); + method public float[] getTimes(); + method public float[] getVolumes(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final android.media.VolumeShaper.Configuration CUBIC_RAMP; + field public static final int INTERPOLATOR_TYPE_CUBIC = 2; // 0x2 + field public static final int INTERPOLATOR_TYPE_CUBIC_MONOTONIC = 3; // 0x3 + field public static final int INTERPOLATOR_TYPE_LINEAR = 1; // 0x1 + field public static final int INTERPOLATOR_TYPE_STEP = 0; // 0x0 + field public static final android.media.VolumeShaper.Configuration LINEAR_RAMP; + field public static final android.media.VolumeShaper.Configuration SCURVE_RAMP; + field public static final android.media.VolumeShaper.Configuration SINE_RAMP; + } + + public static final class VolumeShaper.Configuration.Builder { + ctor public VolumeShaper.Configuration.Builder(); + ctor public VolumeShaper.Configuration.Builder(android.media.VolumeShaper.Configuration); + method public android.media.VolumeShaper.Configuration build(); + method public android.media.VolumeShaper.Configuration.Builder invertVolumes(); + method public android.media.VolumeShaper.Configuration.Builder reflectTimes(); + method public android.media.VolumeShaper.Configuration.Builder scaleToEndVolume(float); + method public android.media.VolumeShaper.Configuration.Builder scaleToStartVolume(float); + method public android.media.VolumeShaper.Configuration.Builder setCurve(float[], float[]); + method public android.media.VolumeShaper.Configuration.Builder setDurationMs(double); + method public android.media.VolumeShaper.Configuration.Builder setInterpolatorType(int); + } + + public static final class VolumeShaper.Operation implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final android.media.VolumeShaper.Operation PLAY; + field public static final android.media.VolumeShaper.Operation REVERSE; + } + } package android.media.audiofx { diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index ddd8a657f75b0..7f5d3a084cb48 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -79,6 +79,7 @@ import com.android.internal.annotations.GuardedBy; */ public class AudioTrack extends PlayerBase implements AudioRouting + , VolumeAutomation { //--------------------------------------------------------- // Constants @@ -1753,6 +1754,12 @@ public class AudioTrack extends PlayerBase return native_getVolumeShaperState(id); } + @Override + public @NonNull VolumeShaper createVolumeShaper( + @NonNull VolumeShaper.Configuration configuration) { + return new VolumeShaper(configuration, this); + } + /** * Sets the playback sample rate for this track. This sets the sampling rate at which * the audio data will be consumed and played back diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 5008a5f8c5c27..1ebbe855a43f0 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -579,6 +579,7 @@ import java.util.Vector; */ public class MediaPlayer extends PlayerBase implements SubtitleController.Listener + , VolumeAutomation { /** Constant to retrieve only the new metadata since the last @@ -1373,6 +1374,12 @@ public class MediaPlayer extends PlayerBase return native_getVolumeShaperState(id); } + @Override + public @NonNull VolumeShaper createVolumeShaper( + @NonNull VolumeShaper.Configuration configuration) { + return new VolumeShaper(configuration, this); + } + private native int native_applyVolumeShaper( @NonNull VolumeShaper.Configuration configuration, @NonNull VolumeShaper.Operation operation); diff --git a/media/java/android/media/VolumeAutomation.java b/media/java/android/media/VolumeAutomation.java new file mode 100644 index 0000000000000..dff8801f95f2e --- /dev/null +++ b/media/java/android/media/VolumeAutomation.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017 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 android.annotation.NonNull; +import android.media.VolumeShaper.Configuration; + +/** + * {@code VolumeAutomation} defines an interface for automatic volume control + * of {@link AudioTrack} and {@link MediaPlayer} objects. + */ +public interface VolumeAutomation { + /** + * Returns a {@link VolumeShaper} object that can be used modify the volume envelope + * of the player or track. + * + * @param configuration the {@link VolumeShaper.Configuration configuration} + * that specifies the curve and duration to use. + * @return a {@code VolumeShaper} object + * @throws IllegalArgumentException if the configuration is not allowed by the player. + * @throws IllegalStateException if too many VolumeShapers are requested or the state of + * the player does not permit its creation (e.g. player is released). + */ + public @NonNull VolumeShaper createVolumeShaper( + @NonNull VolumeShaper.Configuration configuration); +} diff --git a/media/java/android/media/VolumeShaper.java b/media/java/android/media/VolumeShaper.java index 4a2c4d88c50e8..cb27d1089aae9 100644 --- a/media/java/android/media/VolumeShaper.java +++ b/media/java/android/media/VolumeShaper.java @@ -23,78 +23,25 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.AutoCloseable; import java.lang.ref.WeakReference; import java.util.Objects; /** - * TODO: remove @hide * The {@code VolumeShaper} class is used to automatically control audio volume during media - * playback, allowing for simple implementation of transition effects and ducking. + * playback, allowing simple implementation of transition effects and ducking. * * The {@link VolumeShaper} appears as an additional scaling on the audio output, - * and can be used independently of track or stream volume controls. + * and adjusts independently of track or stream volume controls. */ -public final class VolumeShaper { +public final class VolumeShaper implements AutoCloseable { /* member variables */ private int mId; private final WeakReference mWeakPlayerBase; - private final WeakReference mWeakPlayerProxy; - private PlayerProxy mPlayerProxy; - - /** - * Constructs a {@code VolumeShaper} from a {@link VolumeShaper.Configuration} and an - * {@link AudioTrack}. - * - * @param configuration - * @param audioTrack - */ - public VolumeShaper(@NonNull Configuration configuration, @NonNull AudioTrack audioTrack) { - this(configuration, (PlayerBase)audioTrack); - } - - /** - * Constructs a {@code VolumeShaper} from a {@link VolumeShaper.Configuration} and a - * {@link MediaPlayer}. - * - * @param configuration - * @param mediaPlayer - */ - public VolumeShaper(@NonNull Configuration configuration, @NonNull MediaPlayer mediaPlayer) { - this(configuration, (PlayerBase)mediaPlayer); - } /* package */ VolumeShaper( @NonNull Configuration configuration, @NonNull PlayerBase playerBase) { mWeakPlayerBase = new WeakReference(playerBase); - mPlayerProxy = null; - mWeakPlayerProxy = null; - mId = applyPlayer(configuration, new Operation.Builder().defer().build()); - } - - /** - * @hide - * Constructs a {@code VolumeShaper} from a {@link VolumeShaper.Configuration} and a - * {@code PlayerProxy} object. The PlayerProxy object requires that the configuration - * be set with a system VolumeShaper id (this is a reserved value). - * - * @param configuration - * @param playerProxy - */ - public VolumeShaper( - @NonNull Configuration configuration, - @NonNull PlayerProxy playerProxy, - boolean keepReference) { - if (configuration.getId() < 0) { - throw new IllegalArgumentException("playerProxy configuration id must be specified"); - } - if (keepReference) { - mPlayerProxy = playerProxy; - mWeakPlayerProxy = null; - } else { - mWeakPlayerProxy = new WeakReference(playerProxy); - mPlayerProxy = null; - } - mWeakPlayerBase = null; mId = applyPlayer(configuration, new Operation.Builder().defer().build()); } @@ -104,7 +51,7 @@ public final class VolumeShaper { /** * Applies the {@link VolumeShaper.Operation} to the {@code VolumeShaper}. - * @param operation + * @param operation the {@code operation} to apply. */ public void apply(@NonNull Operation operation) { /* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation); @@ -112,17 +59,16 @@ public final class VolumeShaper { /** * Replaces the current {@code VolumeShaper} - * configuration with a new configuration. + * {@code configuration} with a new {@code configuration}. * - * This can be used to dynamically change the {@code VolumeShaper} - * configuration by joining several - * {@code VolumeShaper} configurations together. - * This is useful if the user changes the volume while the - * {@code VolumeShaper} is in effect. + * This allows the user to change the volume shape + * while the existing {@code VolumeShaper} is in effect. * - * @param configuration - * @param operation - * @param join + * @param configuration the new {@code configuration} to use. + * @param operation the operation to apply to the {@code VolumeShaper} + * @param join if true, match the start volume of the + * new {@code configuration} to the current volume of the existing + * {@code VolumeShaper}, to avoid discontinuity. */ public void replace( @NonNull Configuration configuration, @NonNull Operation operation, boolean join) { @@ -141,10 +87,11 @@ public final class VolumeShaper { } /** - * Releases the {@code VolumeShaper}. Any volume scale due to the + * Releases the {@code VolumeShaper} object; any volume scale due to the * {@code VolumeShaper} is removed. */ - public void release() { + @Override + public void close() { try { /* void */ applyPlayer( new VolumeShaper.Configuration(mId), @@ -155,15 +102,11 @@ public final class VolumeShaper { if (mWeakPlayerBase != null) { mWeakPlayerBase.clear(); } - if (mWeakPlayerProxy != null) { - mWeakPlayerProxy.clear(); - } - mPlayerProxy = null; } @Override protected void finalize() { - release(); // ensure we remove the native volume shaper + close(); // ensure we remove the native volume shaper } /** @@ -177,20 +120,7 @@ public final class VolumeShaper { @NonNull VolumeShaper.Configuration configuration, @NonNull VolumeShaper.Operation operation) { final int id; - if (mPlayerProxy != null || mWeakPlayerProxy != null) { - // The PlayerProxy accepts only one way transactions so - // the Configuration must have an id set to one of the system - // ids (a positive value less than 16). - PlayerProxy player = mWeakPlayerProxy != null ? mWeakPlayerProxy.get() : mPlayerProxy; - if (player == null) { - throw new IllegalStateException("player deallocated"); - } - id = configuration.getId(); - if (id < 0) { - throw new IllegalArgumentException("proxy requires configuration with id"); - } - player.applyVolumeShaper(configuration, operation); - } else if (mWeakPlayerBase != null) { + if (mWeakPlayerBase != null) { PlayerBase player = mWeakPlayerBase.get(); if (player == null) { throw new IllegalStateException("player deallocated"); @@ -220,9 +150,7 @@ public final class VolumeShaper { */ private @NonNull VolumeShaper.State getStatePlayer(int id) { final VolumeShaper.State state; - if (mPlayerProxy != null || mWeakPlayerProxy != null) { - throw new IllegalStateException("getStatePlayer not permitted through proxy"); - } else if (mWeakPlayerBase != null) { + if (mWeakPlayerBase != null) { PlayerBase player = mWeakPlayerBase.get(); if (player == null) { throw new IllegalStateException("player deallocated"); @@ -238,11 +166,17 @@ public final class VolumeShaper { } /** - * The {@code VolumeShaper.Configuration} class contains curve shape - * and parameter information for constructing a {@code VolumeShaper}. - * This curve shape and parameter information is specified - * on {@code VolumeShaper} creation - * and may be replaced through {@link VolumeShaper#replace}. + * The {@code VolumeShaper.Configuration} class contains curve + * and duration information. + * It is constructed by the {@link VolumeShaper.Configuration.Builder}. + *

+ * A {@code VolumeShaper.Configuration} is used by + * {@link VolumeAutomation#createVolumeShaper(Configuration) + * VolumeAutomation#createVolumeShaper(Configuration)} to create + * a {@code VolumeShaper} and + * by {@link VolumeShaper#replace(Configuration, Operation, boolean) + * VolumeShaper#replace(Configuration, Operation, boolean)} + * to replace an existing {@code configuration}. */ public static final class Configuration implements Parcelable { private static final int MAXIMUM_CURVE_POINTS = 16; @@ -310,9 +244,9 @@ public final class VolumeShaper { /** * Cubic interpolated volume curve - * with local monotonicity preservation. + * that preserves local monotonicity. * So long as the control points are locally monotonic, - * the curve interpolation will also be locally monotonic. + * the curve interpolation between those points are monotonic. * This is useful for cubic spline interpolated * volume ramps and ducks. */ @@ -328,6 +262,7 @@ public final class VolumeShaper { public @interface OptionFlag {} /** + * @hide * Use a dB full scale volume range for the volume curve. *

* The volume scale is typically from 0.f to 1.f on a linear scale; @@ -337,6 +272,7 @@ public final class VolumeShaper { public static final int OPTION_FLAG_VOLUME_IN_DBFS = (1 << 0); /** + * @hide * Use clock time instead of media time. *

* The default implementation of {@code VolumeShaper} is to apply @@ -354,7 +290,8 @@ public final class VolumeShaper { /** * A one second linear ramp from silence to full volume. - * Use {@link VolumeShaper.Builder#reflectTimes()} to generate + * Use {@link VolumeShaper.Builder#reflectTimes()} + * or {@link VolumeShaper.Builder#invertVolumes()} to generate * the matching linear duck. */ public static final Configuration LINEAR_RAMP = new VolumeShaper.Configuration.Builder() @@ -366,7 +303,8 @@ public final class VolumeShaper { /** * A one second cubic ramp from silence to full volume. - * Use {@link VolumeShaper.Builder#reflectTimes()} to generate + * Use {@link VolumeShaper.Builder#reflectTimes()} + * or {@link VolumeShaper.Builder#invertVolumes()} to generate * the matching cubic duck. */ public static final Configuration CUBIC_RAMP = new VolumeShaper.Configuration.Builder() @@ -377,17 +315,19 @@ public final class VolumeShaper { .build(); /** - * A one second sine curve for energy preserving cross fades. + * A one second sine curve + * from silence to full volume for energy preserving cross fades. * Use {@link VolumeShaper.Builder#reflectTimes()} to generate * the matching cosine duck. */ public static final Configuration SINE_RAMP; /** - * A one second sine-squared s-curve ramp. + * A one second sine-squared s-curve ramp + * from silence to full volume. * Use {@link VolumeShaper.Builder#reflectTimes()} * or {@link VolumeShaper.Builder#invertVolumes()} to generate - * the matching s-curve duck. + * the matching sine-squared s-curve duck. */ public static final Configuration SCURVE_RAMP; @@ -510,6 +450,7 @@ public final class VolumeShaper { }; /** + * @hide * Constructs a volume shaper from an id. * * This is an opaque handle for controlling a {@code VolumeShaper} that has @@ -522,7 +463,7 @@ public final class VolumeShaper { * @param id * @throws IllegalArgumentException if id is negative. */ - private Configuration(int id) { + public Configuration(int id) { if (id < 0) { throw new IllegalArgumentException("negative id " + id); } @@ -557,6 +498,7 @@ public final class VolumeShaper { } /** + * @hide * Returns the {@code VolumeShaper} type. */ public @Type int getType() { @@ -579,6 +521,7 @@ public final class VolumeShaper { } /** + * @hide * Returns the option flags */ public @OptionFlag int getOptionFlags() { @@ -590,7 +533,7 @@ public final class VolumeShaper { } /** - * Returns the duration of the effect in milliseconds. + * Returns the duration of the volume shape in milliseconds. */ public double getDurationMs() { return mDurationMs; @@ -712,21 +655,22 @@ public final class VolumeShaper { private int mType = TYPE_SCALE; private int mId = -1; // invalid private int mInterpolatorType = INTERPOLATOR_TYPE_CUBIC; - private int mOptionFlags = 0; + private int mOptionFlags = OPTION_FLAG_CLOCK_TIME; private double mDurationMs = 1000.; private float[] mTimes = null; private float[] mVolumes = null; /** - * Constructs a new Builder with the defaults. + * Constructs a new {@code Builder} with the defaults. */ public Builder() { } /** - * Constructs a new Builder from a given {@code VolumeShaper.Configuration} + * Constructs a new {@code Builder} with settings + * copied from a given {@code VolumeShaper.Configuration}. * @param configuration prototypical configuration - * which will be reused in the new Builder. + * which will be reused in the new {@code Builder}. */ public Builder(@NonNull Configuration configuration) { mType = configuration.getType(); @@ -740,8 +684,6 @@ public final class VolumeShaper { /** * @hide - * TODO make SystemApi - * * Set the id for system defined shapers. * @param id * @return @@ -757,7 +699,11 @@ public final class VolumeShaper { * If omitted the interplator type is {@link #INTERPOLATOR_TYPE_CUBIC}. * * @param interpolatorType method of interpolation used for the volume curve. - * @return the same Builder instance. + * One of {@link #INTERPOLATOR_TYPE_STEP}, + * {@link #INTERPOLATOR_TYPE_LINEAR}, + * {@link #INTERPOLATOR_TYPE_CUBIC}, + * {@link #INTERPOLATOR_TYPE_CUBIC_MONOTONIC}. + * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code interpolatorType} is not valid. */ public @NonNull Builder setInterpolatorType(@InterpolatorType int interpolatorType) { @@ -776,6 +722,7 @@ public final class VolumeShaper { } /** + * @hide * Sets the optional flags * * If omitted, flags are 0. If {@link #OPTION_FLAG_VOLUME_IN_DBFS} has @@ -783,7 +730,7 @@ public final class VolumeShaper { * volume domain has changed. * * @param optionFlags new value to replace the old {@code optionFlags}. - * @return the same Builder instance. + * @return the same {@code Builder} instance. * @throws IllegalArgumentException if flag is not recognized. */ public @NonNull Builder setOptionFlags(@OptionFlag int optionFlags) { @@ -800,8 +747,9 @@ public final class VolumeShaper { * If omitted, the default duration is 1 second. * * @param durationMs - * @return the same Builder instance. - * @throws IllegalArgumentException if duration is not positive. + * @return the same {@code Builder} instance. + * @throws IllegalArgumentException if {@code durationMs} + * is not strictly positive. */ public @NonNull Builder setDurationMs(double durationMs) { if (durationMs <= 0.) { @@ -823,19 +771,23 @@ public final class VolumeShaper { * and no greater than {@link VolumeShaper.Configuration#getMaximumCurvePoints()}. *

* The volume curve is normalized as follows: - * (1) time (x) coordinates should be monotonically increasing, from 0.f to 1.f; - * (2) volume (y) coordinates must be within 0.f to 1.f for linear and be non-positive - * for log scaling. + * time (x) coordinates should be monotonically increasing, from 0.f to 1.f; + * volume (y) coordinates must be within 0.f to 1.f. *

- * The time scale is set by {@link #setDurationMs} in seconds. + * The time scale is set by {@link #setDurationMs}. *

* @param times an array of float values representing * the time line of the volume curve. * @param volumes an array of float values representing * the amplitude of the volume curve. - * @return the same Builder instance. + * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code times} or {@code volumes} is invalid. */ + + /* Note: volume (y) coordinates must be non-positive for log scaling, + * if {@link VolumeShaper.Configuration#OPTION_FLAG_VOLUME_IN_DBFS} is set. + */ + public @NonNull Builder setCurve(@NonNull float[] times, @NonNull float[] volumes) { String error = checkCurveForErrors( times, volumes, (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0); @@ -852,7 +804,7 @@ public final class VolumeShaper { * the shaper changes volume from the end * to the start. * - * @return the same Builder instance. + * @return the same {@code Builder} instance. */ public @NonNull Builder reflectTimes() { int i; @@ -871,7 +823,7 @@ public final class VolumeShaper { * Inverts the volume curve so that the max volume * becomes the min volume and vice versa. * - * @return the same Builder instance. + * @return the same {@code Builder} instance. */ public @NonNull Builder invertVolumes() { if (mVolumes.length >= 2) { @@ -899,8 +851,9 @@ public final class VolumeShaper { * Keeps the start volume the same. * This works best if the volume curve is monotonic. * - * @return the same Builder instance. - * @throws IllegalArgumentException if volume is not valid. + * @param volume the target end volume to use. + * @return the same {@code Builder} instance. + * @throws IllegalArgumentException if {@code volume} is not valid. */ public @NonNull Builder scaleToEndVolume(float volume) { final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0; @@ -930,8 +883,9 @@ public final class VolumeShaper { * Keeps the end volume the same. * This works best if the volume curve is monotonic. * - * @return the same Builder instance. - * @throws IllegalArgumentException if volume is not valid. + * @param volume the target start volume to use. + * @return the same {@code Builder} instance. + * @throws IllegalArgumentException if {@code volume} is not valid. */ public @NonNull Builder scaleToStartVolume(float volume) { final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0; @@ -978,6 +932,8 @@ public final class VolumeShaper { public static final class Operation implements Parcelable { /** * Forward playback from current volume time position. + * At the end of the {@code VolumeShaper} curve, + * the last volume value persists. */ public static final Operation PLAY = new VolumeShaper.Operation.Builder() @@ -985,6 +941,8 @@ public final class VolumeShaper { /** * Reverse playback from current volume time position. + * When the position reaches the start of the {@code VolumeShaper} curve, + * the first volume value persists. */ public static final Operation REVERSE = new VolumeShaper.Operation.Builder() @@ -1039,6 +997,13 @@ public final class VolumeShaper { */ private static final int FLAG_DEFER = 1 << 3; + /** + * Use the id specified in the configuration, creating + * VolumeShaper as needed; the configuration should be + * TYPE_SCALE. + */ + private static final int FLAG_CREATE_IF_NEEDED = 1 << 4; + private static final int FLAG_PUBLIC_ALL = FLAG_REVERSE | FLAG_TERMINATE; private final int mFlags; @@ -1126,15 +1091,13 @@ public final class VolumeShaper { } /** - * Replaces the previous {@code VolumeShaper}. + * Replaces the previous {@code VolumeShaper} specified by id. * It has no other effect if the {@code VolumeShaper} is - * already expired. If the replaceId is the same as the id associated with - * the {@code VolumeShaper} in a {@code setVolumeShaper()} call, - * an error is returned. - * @param handle is a previous volumeShaper {@code VolumeShaper}. - * @param join the start to match the current volume of the previous - * shaper. - * @return the same Builder instance. + * already expired. + * @param id the id of the previous {@code VolumeShaper}. + * @param join if true, match the volume of the previous + * shaper to the start volume of the new {@code VolumeShaper}. + * @return the same {@code Builder} instance. */ public @NonNull Builder replace(int id, boolean join) { mReplaceId = id; @@ -1148,7 +1111,7 @@ public final class VolumeShaper { /** * Defers all operations. - * @return the same Builder instance. + * @return the same {@code Builder} instance. */ public @NonNull Builder defer() { mFlags |= FLAG_DEFER; @@ -1158,7 +1121,7 @@ public final class VolumeShaper { /** * Terminates the VolumeShaper. * Do not call directly, use {@link VolumeShaper#release()}. - * @return the same Builder instance. + * @return the same {@code Builder} instance. */ public @NonNull Builder terminate() { mFlags |= FLAG_TERMINATE; @@ -1167,19 +1130,30 @@ public final class VolumeShaper { /** * Reverses direction. - * @return the same Builder instance. + * @return the same {@code Builder} instance. */ public @NonNull Builder reverse() { mFlags ^= FLAG_REVERSE; return this; } + /** + * Use the id specified in the configuration, creating + * VolumeShaper as needed; the configuration should be + * TYPE_SCALE. + * @return the same {@code Builder} instance. + */ + public @NonNull Builder createIfNeeded() { + mFlags |= FLAG_CREATE_IF_NEEDED; + return this; + } + /** * Sets the operation flag. Do not call this directly but one of the * other builder methods. * * @param flags new value for {@code flags}, consisting of ORed flags. - * @return the same Builder instance. + * @return the same {@code Builder} instance. */ private @NonNull Builder setFlags(@Flag int flags) { if ((flags & ~FLAG_PUBLIC_ALL) != 0) { diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index e8ff510242ff4..d648dd82dc9aa 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -51,14 +51,36 @@ public final class PlaybackActivityMonitor private final static boolean DEBUG = false; private final static int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1; - private ArrayList mClients = new ArrayList(); + private final VolumeShaper.Configuration DUCK_VSHAPE = + new VolumeShaper.Configuration.Builder() + .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID) + .setCurve(new float[] { 0.f, 1.f } /* times */, + new float[] { 1.f, 0.2f } /* volumes */) + .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME) + .setDurationMs(MediaFocusControl.getFocusRampTimeMs( + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, + new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build())) + .build(); + private final VolumeShaper.Configuration DUCK_ID = + new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_DUCK_ID); + private final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED = + new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY) + .createIfNeeded() + .build(); + private final VolumeShaper.Operation TERMINATE = + new VolumeShaper.Operation.Builder() + .terminate() + .build(); + + private final ArrayList mClients = new ArrayList(); // a public client is one that needs an anonymized version of the playback configurations, we // keep track of whether there is at least one to know when we need to create the list of // playback configurations that do not contain uid/pid/package name information. private boolean mHasPublicClients = false; private final Object mPlayerLock = new Object(); - private HashMap mPlayers = + private final HashMap mPlayers = new HashMap(); PlaybackActivityMonitor() { @@ -130,12 +152,10 @@ public final class PlaybackActivityMonitor synchronized(mPlayerLock) { final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid)); if (checkConfigurationCaller(piid, apc, binderUid)) { + apc.getPlayerProxy().applyVolumeShaper( + DUCK_ID, + TERMINATE); mPlayers.remove(new Integer(piid)); - final VolumeShaper vs = mDuckVolumeShapers.get(new Integer(piid)); - if (vs != null) { - vs.release(); - mDuckVolumeShapers.remove(new Integer(piid)); - } } else { Log.e(TAG, "Error releasing player " + piid); } @@ -252,20 +272,6 @@ public final class PlaybackActivityMonitor private final ArrayList mDuckedPlayers = new ArrayList(); private final ArrayList mMutedPlayers = new ArrayList(); - private final VolumeShaper.Configuration DUCK_VSHAPE = - new VolumeShaper.Configuration.Builder() - .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID) - .setCurve(new float[] { 0.f, 1.f } /* times */, - new float[] { 1.f, 0.2f } /* volumes */) - .setDurationMs(MediaFocusControl.getFocusRampTimeMs( - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, - new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION) - .build())) - .build(); - - private final HashMap mDuckVolumeShapers = - new HashMap(); - @Override public boolean duckPlayers(FocusRequester winner, FocusRequester loser) { if (DEBUG) { @@ -302,17 +308,9 @@ public final class PlaybackActivityMonitor } else { try { if (DEBUG) { Log.v(TAG, "ducking player " + piid); } - final VolumeShaper ducker; - if (mDuckVolumeShapers.containsKey(new Integer(piid))) { - ducker = mDuckVolumeShapers.get(new Integer(piid)); - } else { - ducker = new VolumeShaper( - DUCK_VSHAPE, - apc.getPlayerProxy(), - true /* keepReference */); - mDuckVolumeShapers.put(new Integer(piid), ducker); - } - ducker.apply(VolumeShaper.Operation.PLAY); // duck + apc.getPlayerProxy().applyVolumeShaper( + DUCK_VSHAPE, + PLAY_CREATE_IF_NEEDED); mDuckedPlayers.add(piid); } catch (Exception e) { Log.e(TAG, "Error ducking player " + piid, e); @@ -341,10 +339,9 @@ public final class PlaybackActivityMonitor try { if (DEBUG) { Log.v(TAG, "unducking player" + piid); } mDuckedPlayers.remove(new Integer(piid)); - if (mDuckVolumeShapers.containsKey(new Integer(piid))) { - final VolumeShaper ducker = mDuckVolumeShapers.get(new Integer(piid)); - ducker.apply(VolumeShaper.Operation.REVERSE); // unduck - } + apc.getPlayerProxy().applyVolumeShaper( + DUCK_ID, + VolumeShaper.Operation.REVERSE); } catch (Exception e) { Log.e(TAG, "Error unducking player " + piid, e); }