Loading media/java/android/media/VolumeAutomation.java +3 −3 Original line number Original line Diff line number Diff line Loading @@ -31,9 +31,9 @@ public interface VolumeAutomation { * @param configuration the {@link VolumeShaper.Configuration configuration} * @param configuration the {@link VolumeShaper.Configuration configuration} * that specifies the curve and duration to use. * that specifies the curve and duration to use. * @return a {@code VolumeShaper} object * @return a {@code VolumeShaper} object * @throws IllegalArgumentException if the configuration is not allowed by the player. * @throws IllegalArgumentException if the {@code configuration} is not allowed by the player. * @throws IllegalStateException if too many VolumeShapers are requested or the state of * @throws IllegalStateException if too many {@code VolumeShaper}s are requested * the player does not permit its creation (e.g. player is released). * or the state of the player does not permit its creation (e.g. player is released). */ */ public @NonNull VolumeShaper createVolumeShaper( public @NonNull VolumeShaper createVolumeShaper( @NonNull VolumeShaper.Configuration configuration); @NonNull VolumeShaper.Configuration configuration); Loading media/java/android/media/VolumeShaper.java +122 −33 Original line number Original line Diff line number Diff line Loading @@ -31,8 +31,16 @@ import java.util.Objects; /** /** * The {@code VolumeShaper} class is used to automatically control audio volume during media * The {@code VolumeShaper} class is used to automatically control audio volume during media * playback, allowing simple implementation of transition effects and ducking. * playback, allowing simple implementation of transition effects and ducking. * It is created from implementations of {@code VolumeAutomation}, * such as {@code MediaPlayer} and {@code AudioTrack} (referred to as "players" below), * by {@link MediaPlayer#createVolumeShaper} or {@link AudioTrack#createVolumeShaper}. * * * The {@link VolumeShaper} appears as an additional scaling on the audio output, * A {@code VolumeShaper} is intended for short volume changes. * If the audio output sink changes during * a {@code VolumeShaper} transition, the precise curve position may be lost, and the * {@code VolumeShaper} may advance to the end of the curve for the new audio output sink. * * The {@code VolumeShaper} appears as an additional scaling on the audio output, * and adjusts independently of track or stream volume controls. * and adjusts independently of track or stream volume controls. */ */ public final class VolumeShaper implements AutoCloseable { public final class VolumeShaper implements AutoCloseable { Loading @@ -52,7 +60,19 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Applies the {@link VolumeShaper.Operation} to the {@code VolumeShaper}. * Applies the {@link VolumeShaper.Operation} to the {@code VolumeShaper}. * * Applying {@link VolumeShaper.Operation#PLAY} after {@code PLAY} * or {@link VolumeShaper.Operation#REVERSE} after * {@code REVERSE} has no effect. * * Applying {@link VolumeShaper.Operation#PLAY} when the player * hasn't started will synchronously start the {@code VolumeShaper} when * playback begins. * * @param operation the {@code operation} to apply. * @param operation the {@code operation} to apply. * @throws IllegalStateException if the player is uninitialized or if there * is a critical failure. In that case, the {@code VolumeShaper} should be * recreated. */ */ public void apply(@NonNull Operation operation) { public void apply(@NonNull Operation operation) { /* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation); /* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation); Loading @@ -65,11 +85,24 @@ public final class VolumeShaper implements AutoCloseable { * This allows the user to change the volume shape * This allows the user to change the volume shape * while the existing {@code VolumeShaper} is in effect. * while the existing {@code VolumeShaper} is in effect. * * * The effect of {@code replace()} is similar to an atomic close of * the existing {@code VolumeShaper} and creation of a new {@code VolumeShaper}. * * If the {@code operation} is {@link VolumeShaper.Operation#PLAY} then the * new curve starts immediately. * * If the {@code operation} is * {@link VolumeShaper.Operation#REVERSE}, then the new curve will * be delayed until {@code PLAY} is applied. * * @param configuration the new {@code configuration} to use. * @param configuration the new {@code configuration} to use. * @param operation the operation to apply to the {@code VolumeShaper} * @param operation the {@code operation} to apply to the {@code VolumeShaper} * @param join if true, match the start volume of the * @param join if true, match the start volume of the * new {@code configuration} to the current volume of the existing * new {@code configuration} to the current volume of the existing * {@code VolumeShaper}, to avoid discontinuity. * {@code VolumeShaper}, to avoid discontinuity. * @throws IllegalStateException if the player is uninitialized or if there * is a critical failure. In that case, the {@code VolumeShaper} should be * recreated. */ */ public void replace( public void replace( @NonNull Configuration configuration, @NonNull Operation operation, boolean join) { @NonNull Configuration configuration, @NonNull Operation operation, boolean join) { Loading @@ -81,7 +114,14 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Returns the current volume scale attributable to the {@code VolumeShaper}. * Returns the current volume scale attributable to the {@code VolumeShaper}. * * * This is the last volume from the {@code VolumeShaper} used for the player, * or the initial volume if the {@code VolumeShaper} hasn't been started with * {@link VolumeShaper.Operation#PLAY}. * * @return the volume, linearly represented as a value between 0.f and 1.f. * @return the volume, linearly represented as a value between 0.f and 1.f. * @throws IllegalStateException if the player is uninitialized or if there * is a critical failure. In that case, the {@code VolumeShaper} should be * recreated. */ */ public float getVolume() { public float getVolume() { return getStatePlayer(mId).getVolume(); return getStatePlayer(mId).getVolume(); Loading @@ -89,7 +129,14 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Releases the {@code VolumeShaper} object; any volume scale due to the * Releases the {@code VolumeShaper} object; any volume scale due to the * {@code VolumeShaper} is removed. * {@code VolumeShaper} is removed after closing. * * If the volume does not reach 1.f when the {@code VolumeShaper} is closed * (or finalized), there may be an abrupt change of volume. * * {@code close()} may be safely called after a prior {@code close()}. * This class implements the Java {@code AutoClosable} interface and * may be used with try-with-resources. */ */ @Override @Override public void close() { public void close() { Loading @@ -107,11 +154,11 @@ public final class VolumeShaper implements AutoCloseable { @Override @Override protected void finalize() { protected void finalize() { close(); // ensure we remove the native volume shaper close(); // ensure we remove the native VolumeShaper } } /** /** * Internal call to apply the configuration and operation to the Player. * Internal call to apply the {@code configuration} and {@code operation} to the player. * Returns a valid shaper id or throws the appropriate exception. * Returns a valid shaper id or throws the appropriate exception. * @param configuration * @param configuration * @param operation * @param operation Loading @@ -137,7 +184,7 @@ public final class VolumeShaper implements AutoCloseable { // Due to RPC handling, we translate integer codes to exceptions right before // Due to RPC handling, we translate integer codes to exceptions right before // delivering to the user. // delivering to the user. if (id == VOLUME_SHAPER_INVALID_OPERATION) { if (id == VOLUME_SHAPER_INVALID_OPERATION) { throw new IllegalStateException("player or volume shaper deallocated"); throw new IllegalStateException("player or VolumeShaper deallocated"); } else { } else { throw new IllegalArgumentException("invalid configuration or operation: " + id); throw new IllegalArgumentException("invalid configuration or operation: " + id); } } Loading @@ -146,9 +193,9 @@ public final class VolumeShaper implements AutoCloseable { } } /** /** * Internal call to retrieve the current VolumeShaper state. * Internal call to retrieve the current {@code VolumeShaper} state. * @param id * @param id * @return the current {@vode VolumeShaper.State} * @return the current {@code VolumeShaper.State} * @throws IllegalStateException if the player has been deallocated or is uninitialized. * @throws IllegalStateException if the player has been deallocated or is uninitialized. */ */ private @NonNull VolumeShaper.State getStatePlayer(int id) { private @NonNull VolumeShaper.State getStatePlayer(int id) { Loading Loading @@ -180,6 +227,9 @@ public final class VolumeShaper implements AutoCloseable { * by {@link VolumeShaper#replace(Configuration, Operation, boolean) * by {@link VolumeShaper#replace(Configuration, Operation, boolean) * VolumeShaper.replace(Configuration, Operation, boolean)} * VolumeShaper.replace(Configuration, Operation, boolean)} * to replace an existing {@code configuration}. * to replace an existing {@code configuration}. * <p> * The {@link AudioTrack} and {@link MediaPlayer} classes implement * the {@link VolumeAutomation} interface. */ */ public static final class Configuration implements Parcelable { public static final class Configuration implements Parcelable { private static final int MAXIMUM_CURVE_POINTS = 16; private static final int MAXIMUM_CURVE_POINTS = 16; Loading Loading @@ -485,7 +535,7 @@ public final class VolumeShaper implements AutoCloseable { /** /** * @hide * @hide * Constructs a volume shaper from an id. * Constructs a {@code VolumeShaper} from an id. * * * This is an opaque handle for controlling a {@code VolumeShaper} that has * This is an opaque handle for controlling a {@code VolumeShaper} that has * already been sent to a player. The {@code id} is returned from the * already been sent to a player. The {@code id} is returned from the Loading Loading @@ -756,7 +806,7 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Sets the interpolator type. * Sets the interpolator type. * * * If omitted the interplator type is {@link #INTERPOLATOR_TYPE_CUBIC}. * If omitted the default interpolator type is {@link #INTERPOLATOR_TYPE_CUBIC}. * * * @param interpolatorType method of interpolation used for the volume curve. * @param interpolatorType method of interpolation used for the volume curve. * One of {@link #INTERPOLATOR_TYPE_STEP}, * One of {@link #INTERPOLATOR_TYPE_STEP}, Loading Loading @@ -802,7 +852,7 @@ public final class VolumeShaper implements AutoCloseable { } } /** /** * Sets the volume shaper duration in milliseconds. * Sets the {@code VolumeShaper} duration in milliseconds. * * * If omitted, the default duration is 1 second. * If omitted, the default duration is 1 second. * * Loading Loading @@ -1059,13 +1109,13 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Defer playback until next operation is sent. This is used * Defer playback until next operation is sent. This is used * when starting a VolumeShaper effect. * when starting a {@code VolumeShaper} effect. */ */ private static final int FLAG_DEFER = 1 << 3; private static final int FLAG_DEFER = 1 << 3; /** /** * Use the id specified in the configuration, creating * Use the id specified in the configuration, creating * VolumeShaper as needed; the configuration should be * {@code VolumeShaper} as needed; the configuration should be * TYPE_SCALE. * TYPE_SCALE. */ */ private static final int FLAG_CREATE_IF_NEEDED = 1 << 4; private static final int FLAG_CREATE_IF_NEEDED = 1 << 4; Loading @@ -1074,18 +1124,20 @@ public final class VolumeShaper implements AutoCloseable { private final int mFlags; private final int mFlags; private final int mReplaceId; private final int mReplaceId; private final float mXOffset; @Override @Override public String toString() { public String toString() { return "VolumeShaper.Operation{" return "VolumeShaper.Operation{" + "mFlags = 0x" + Integer.toHexString(mFlags).toUpperCase() + "mFlags = 0x" + Integer.toHexString(mFlags).toUpperCase() + ", mReplaceId = " + mReplaceId + ", mReplaceId = " + mReplaceId + ", mXOffset = " + mXOffset + "}"; + "}"; } } @Override @Override public int hashCode() { public int hashCode() { return Objects.hash(mFlags, mReplaceId); return Objects.hash(mFlags, mReplaceId, mXOffset); } } @Override @Override Loading @@ -1093,10 +1145,10 @@ public final class VolumeShaper implements AutoCloseable { if (!(o instanceof Operation)) return false; if (!(o instanceof Operation)) return false; if (o == this) return true; if (o == this) return true; final Operation other = (Operation) o; final Operation other = (Operation) o; // if xOffset (native field only) is brought into Java // we need to do proper NaN comparison as that is allowed. return mFlags == other.mFlags return mFlags == other.mFlags && mReplaceId == other.mReplaceId; && mReplaceId == other.mReplaceId && Float.compare(mXOffset, other.mXOffset) == 0; } } @Override @Override Loading @@ -1109,7 +1161,7 @@ public final class VolumeShaper implements AutoCloseable { // this needs to match the native VolumeShaper.Operation parceling // this needs to match the native VolumeShaper.Operation parceling dest.writeInt(mFlags); dest.writeInt(mFlags); dest.writeInt(mReplaceId); dest.writeInt(mReplaceId); dest.writeFloat(Float.NaN); // xOffset (ignored at Java level) dest.writeFloat(mXOffset); } } public static final Parcelable.Creator<VolumeShaper.Operation> CREATOR public static final Parcelable.Creator<VolumeShaper.Operation> CREATOR Loading @@ -1119,11 +1171,12 @@ public final class VolumeShaper implements AutoCloseable { // this needs to match the native VolumeShaper.Operation parceling // this needs to match the native VolumeShaper.Operation parceling final int flags = p.readInt(); final int flags = p.readInt(); final int replaceId = p.readInt(); final int replaceId = p.readInt(); final float xOffset = p.readFloat(); // ignored at Java level final float xOffset = p.readFloat(); return new VolumeShaper.Operation( return new VolumeShaper.Operation( flags flags , replaceId); , replaceId , xOffset); } } @Override @Override Loading @@ -1132,9 +1185,10 @@ public final class VolumeShaper implements AutoCloseable { } } }; }; private Operation(@Flag int flags, int replaceId) { private Operation(@Flag int flags, int replaceId, float xOffset) { mFlags = flags; mFlags = flags; mReplaceId = replaceId; mReplaceId = replaceId; mXOffset = xOffset; } } /** /** Loading @@ -1146,6 +1200,7 @@ public final class VolumeShaper implements AutoCloseable { public static final class Builder { public static final class Builder { int mFlags; int mFlags; int mReplaceId; int mReplaceId; float mXOffset; /** /** * Constructs a new {@code Builder} with the defaults. * Constructs a new {@code Builder} with the defaults. Loading @@ -1153,23 +1208,27 @@ public final class VolumeShaper implements AutoCloseable { public Builder() { public Builder() { mFlags = 0; mFlags = 0; mReplaceId = -1; mReplaceId = -1; mXOffset = Float.NaN; } } /** /** * Constructs a new Builder from a given {@code VolumeShaper.Operation} * Constructs a new {@code Builder} from a given {@code VolumeShaper.Operation} * @param operation the {@code VolumeShaper.operation} whose data will be * @param operation the {@code VolumeShaper.operation} whose data will be * reused in the new Builder. * reused in the new {@code Builder}. */ */ public Builder(@NonNull VolumeShaper.Operation operation) { public Builder(@NonNull VolumeShaper.Operation operation) { mReplaceId = operation.mReplaceId; mReplaceId = operation.mReplaceId; mFlags = operation.mFlags; mFlags = operation.mFlags; mXOffset = operation.mXOffset; } } /** /** * Replaces the previous {@code VolumeShaper} specified by id. * Replaces the previous {@code VolumeShaper} specified by {@code id}. * It has no other effect if the {@code VolumeShaper} is * * already expired. * The {@code VolumeShaper} specified by the {@code id} is removed * @param id the id of the previous {@code VolumeShaper}. * if it exists. The configuration should be TYPE_SCALE. * * @param id the {@code id} of the previous {@code VolumeShaper}. * @param join if true, match the volume of the previous * @param join if true, match the volume of the previous * shaper to the start volume of the new {@code VolumeShaper}. * shaper to the start volume of the new {@code VolumeShaper}. * @return the same {@code Builder} instance. * @return the same {@code Builder} instance. Loading @@ -1194,8 +1253,9 @@ public final class VolumeShaper implements AutoCloseable { } } /** /** * Terminates the VolumeShaper. * Terminates the {@code VolumeShaper}. * Do not call directly, use {@link VolumeShaper#release()}. * * Do not call directly, use {@link VolumeShaper#close()}. * @return the same {@code Builder} instance. * @return the same {@code Builder} instance. */ */ public @NonNull Builder terminate() { public @NonNull Builder terminate() { Loading @@ -1214,8 +1274,12 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Use the id specified in the configuration, creating * Use the id specified in the configuration, creating * VolumeShaper as needed; the configuration should be * {@code VolumeShaper} only as needed; the configuration should be * TYPE_SCALE. * TYPE_SCALE. * * If the {@code VolumeShaper} with the same id already exists * then the operation has no effect. * * @return the same {@code Builder} instance. * @return the same {@code Builder} instance. */ */ public @NonNull Builder createIfNeeded() { public @NonNull Builder createIfNeeded() { Loading @@ -1223,6 +1287,28 @@ public final class VolumeShaper implements AutoCloseable { return this; return this; } } /** * Sets the {@code xOffset} to use for the {@code VolumeShaper}. * * The {@code xOffset} is the position on the volume curve, * and setting takes effect when the {@code VolumeShaper} is used next. * * @param xOffset a value between (or equal to) 0.f and 1.f, or Float.NaN to ignore. * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code xOffset} is not between 0.f and 1.f, * or a Float.NaN. */ public @NonNull Builder setXOffset(float xOffset) { if (xOffset < -0.f) { throw new IllegalArgumentException("Negative xOffset not allowed"); } else if (xOffset > 1.f) { throw new IllegalArgumentException("xOffset > 1.f not allowed"); } // Float.NaN passes through mXOffset = xOffset; return this; } /** /** * Sets the operation flag. Do not call this directly but one of the * Sets the operation flag. Do not call this directly but one of the * other builder methods. * other builder methods. Loading @@ -1245,7 +1331,7 @@ public final class VolumeShaper implements AutoCloseable { * @return a new {@code VolumeShaper.Operation} object * @return a new {@code VolumeShaper.Operation} object */ */ public @NonNull Operation build() { public @NonNull Operation build() { return new Operation(mFlags, mReplaceId); return new Operation(mFlags, mReplaceId, mXOffset); } } } // Operation.Builder } // Operation.Builder } // Operation } // Operation Loading Loading @@ -1316,15 +1402,18 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Gets the volume of the {@link VolumeShaper.State}. * Gets the volume of the {@link VolumeShaper.State}. * @return linear volume between 0.f and 1.f. */ */ public float getVolume() { public float getVolume() { return mVolume; return mVolume; } } /** /** * Gets the elapsed ms of the {@link VolumeShaper.State} * Gets the {@code xOffset} position on the normalized curve * of the {@link VolumeShaper.State}. * @return the curve x position between 0.f and 1.f. */ */ public double getXOffset() { public float getXOffset() { return mXOffset; return mXOffset; } } } // State } // State Loading media/jni/android_media_VolumeShaper.h +8 −3 Original line number Original line Diff line number Diff line Loading @@ -40,6 +40,7 @@ struct VolumeShaperHelper { jmethodID opConstructId; jmethodID opConstructId; jfieldID opFlagsId; jfieldID opFlagsId; jfieldID opReplaceIdId; jfieldID opReplaceIdId; jfieldID opXOffsetId; // VolumeShaper.State // VolumeShaper.State jclass stClazz; jclass stClazz; Loading Loading @@ -74,9 +75,10 @@ struct VolumeShaperHelper { if (opClazz == nullptr) { if (opClazz == nullptr) { return; return; } } opConstructId = env->GetMethodID(opClazz, "<init>", "(II)V"); opConstructId = env->GetMethodID(opClazz, "<init>", "(IIF)V"); opFlagsId = env->GetFieldID(opClazz, "mFlags", "I"); opFlagsId = env->GetFieldID(opClazz, "mFlags", "I"); opReplaceIdId = env->GetFieldID(opClazz, "mReplaceId", "I"); opReplaceIdId = env->GetFieldID(opClazz, "mReplaceId", "I"); opXOffsetId = env->GetFieldID(opClazz, "mXOffset", "F"); env->DeleteLocalRef(lclazz); env->DeleteLocalRef(lclazz); lclazz = env->FindClass("android/media/VolumeShaper$State"); lclazz = env->FindClass("android/media/VolumeShaper$State"); Loading Loading @@ -179,17 +181,20 @@ struct VolumeShaperHelper { VolumeShaper::Operation::Flag flags = VolumeShaper::Operation::Flag flags = (VolumeShaper::Operation::Flag)env->GetIntField(joperation, fields.opFlagsId); (VolumeShaper::Operation::Flag)env->GetIntField(joperation, fields.opFlagsId); int replaceId = env->GetIntField(joperation, fields.opReplaceIdId); int replaceId = env->GetIntField(joperation, fields.opReplaceIdId); float xOffset = env->GetFloatField(joperation, fields.opXOffsetId); sp<VolumeShaper::Operation> operation = new VolumeShaper::Operation(flags, replaceId); sp<VolumeShaper::Operation> operation = new VolumeShaper::Operation(flags, replaceId, xOffset); return operation; return operation; } } static jobject convertOperationToJobject( static jobject convertOperationToJobject( JNIEnv *env, const fields_t &fields, const sp<VolumeShaper::Operation> &operation) { JNIEnv *env, const fields_t &fields, const sp<VolumeShaper::Operation> &operation) { // prepare constructor args // prepare constructor args jvalue args[2]; jvalue args[3]; args[0].i = (jint)operation->getFlags(); args[0].i = (jint)operation->getFlags(); args[1].i = (jint)operation->getReplaceId(); args[1].i = (jint)operation->getReplaceId(); args[2].f = (jfloat)operation->getXOffset(); jobject joperation = env->NewObjectA(fields.opClazz, fields.opConstructId, args); jobject joperation = env->NewObjectA(fields.opClazz, fields.opConstructId, args); return joperation; return joperation; Loading Loading
media/java/android/media/VolumeAutomation.java +3 −3 Original line number Original line Diff line number Diff line Loading @@ -31,9 +31,9 @@ public interface VolumeAutomation { * @param configuration the {@link VolumeShaper.Configuration configuration} * @param configuration the {@link VolumeShaper.Configuration configuration} * that specifies the curve and duration to use. * that specifies the curve and duration to use. * @return a {@code VolumeShaper} object * @return a {@code VolumeShaper} object * @throws IllegalArgumentException if the configuration is not allowed by the player. * @throws IllegalArgumentException if the {@code configuration} is not allowed by the player. * @throws IllegalStateException if too many VolumeShapers are requested or the state of * @throws IllegalStateException if too many {@code VolumeShaper}s are requested * the player does not permit its creation (e.g. player is released). * or the state of the player does not permit its creation (e.g. player is released). */ */ public @NonNull VolumeShaper createVolumeShaper( public @NonNull VolumeShaper createVolumeShaper( @NonNull VolumeShaper.Configuration configuration); @NonNull VolumeShaper.Configuration configuration); Loading
media/java/android/media/VolumeShaper.java +122 −33 Original line number Original line Diff line number Diff line Loading @@ -31,8 +31,16 @@ import java.util.Objects; /** /** * The {@code VolumeShaper} class is used to automatically control audio volume during media * The {@code VolumeShaper} class is used to automatically control audio volume during media * playback, allowing simple implementation of transition effects and ducking. * playback, allowing simple implementation of transition effects and ducking. * It is created from implementations of {@code VolumeAutomation}, * such as {@code MediaPlayer} and {@code AudioTrack} (referred to as "players" below), * by {@link MediaPlayer#createVolumeShaper} or {@link AudioTrack#createVolumeShaper}. * * * The {@link VolumeShaper} appears as an additional scaling on the audio output, * A {@code VolumeShaper} is intended for short volume changes. * If the audio output sink changes during * a {@code VolumeShaper} transition, the precise curve position may be lost, and the * {@code VolumeShaper} may advance to the end of the curve for the new audio output sink. * * The {@code VolumeShaper} appears as an additional scaling on the audio output, * and adjusts independently of track or stream volume controls. * and adjusts independently of track or stream volume controls. */ */ public final class VolumeShaper implements AutoCloseable { public final class VolumeShaper implements AutoCloseable { Loading @@ -52,7 +60,19 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Applies the {@link VolumeShaper.Operation} to the {@code VolumeShaper}. * Applies the {@link VolumeShaper.Operation} to the {@code VolumeShaper}. * * Applying {@link VolumeShaper.Operation#PLAY} after {@code PLAY} * or {@link VolumeShaper.Operation#REVERSE} after * {@code REVERSE} has no effect. * * Applying {@link VolumeShaper.Operation#PLAY} when the player * hasn't started will synchronously start the {@code VolumeShaper} when * playback begins. * * @param operation the {@code operation} to apply. * @param operation the {@code operation} to apply. * @throws IllegalStateException if the player is uninitialized or if there * is a critical failure. In that case, the {@code VolumeShaper} should be * recreated. */ */ public void apply(@NonNull Operation operation) { public void apply(@NonNull Operation operation) { /* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation); /* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation); Loading @@ -65,11 +85,24 @@ public final class VolumeShaper implements AutoCloseable { * This allows the user to change the volume shape * This allows the user to change the volume shape * while the existing {@code VolumeShaper} is in effect. * while the existing {@code VolumeShaper} is in effect. * * * The effect of {@code replace()} is similar to an atomic close of * the existing {@code VolumeShaper} and creation of a new {@code VolumeShaper}. * * If the {@code operation} is {@link VolumeShaper.Operation#PLAY} then the * new curve starts immediately. * * If the {@code operation} is * {@link VolumeShaper.Operation#REVERSE}, then the new curve will * be delayed until {@code PLAY} is applied. * * @param configuration the new {@code configuration} to use. * @param configuration the new {@code configuration} to use. * @param operation the operation to apply to the {@code VolumeShaper} * @param operation the {@code operation} to apply to the {@code VolumeShaper} * @param join if true, match the start volume of the * @param join if true, match the start volume of the * new {@code configuration} to the current volume of the existing * new {@code configuration} to the current volume of the existing * {@code VolumeShaper}, to avoid discontinuity. * {@code VolumeShaper}, to avoid discontinuity. * @throws IllegalStateException if the player is uninitialized or if there * is a critical failure. In that case, the {@code VolumeShaper} should be * recreated. */ */ public void replace( public void replace( @NonNull Configuration configuration, @NonNull Operation operation, boolean join) { @NonNull Configuration configuration, @NonNull Operation operation, boolean join) { Loading @@ -81,7 +114,14 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Returns the current volume scale attributable to the {@code VolumeShaper}. * Returns the current volume scale attributable to the {@code VolumeShaper}. * * * This is the last volume from the {@code VolumeShaper} used for the player, * or the initial volume if the {@code VolumeShaper} hasn't been started with * {@link VolumeShaper.Operation#PLAY}. * * @return the volume, linearly represented as a value between 0.f and 1.f. * @return the volume, linearly represented as a value between 0.f and 1.f. * @throws IllegalStateException if the player is uninitialized or if there * is a critical failure. In that case, the {@code VolumeShaper} should be * recreated. */ */ public float getVolume() { public float getVolume() { return getStatePlayer(mId).getVolume(); return getStatePlayer(mId).getVolume(); Loading @@ -89,7 +129,14 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Releases the {@code VolumeShaper} object; any volume scale due to the * Releases the {@code VolumeShaper} object; any volume scale due to the * {@code VolumeShaper} is removed. * {@code VolumeShaper} is removed after closing. * * If the volume does not reach 1.f when the {@code VolumeShaper} is closed * (or finalized), there may be an abrupt change of volume. * * {@code close()} may be safely called after a prior {@code close()}. * This class implements the Java {@code AutoClosable} interface and * may be used with try-with-resources. */ */ @Override @Override public void close() { public void close() { Loading @@ -107,11 +154,11 @@ public final class VolumeShaper implements AutoCloseable { @Override @Override protected void finalize() { protected void finalize() { close(); // ensure we remove the native volume shaper close(); // ensure we remove the native VolumeShaper } } /** /** * Internal call to apply the configuration and operation to the Player. * Internal call to apply the {@code configuration} and {@code operation} to the player. * Returns a valid shaper id or throws the appropriate exception. * Returns a valid shaper id or throws the appropriate exception. * @param configuration * @param configuration * @param operation * @param operation Loading @@ -137,7 +184,7 @@ public final class VolumeShaper implements AutoCloseable { // Due to RPC handling, we translate integer codes to exceptions right before // Due to RPC handling, we translate integer codes to exceptions right before // delivering to the user. // delivering to the user. if (id == VOLUME_SHAPER_INVALID_OPERATION) { if (id == VOLUME_SHAPER_INVALID_OPERATION) { throw new IllegalStateException("player or volume shaper deallocated"); throw new IllegalStateException("player or VolumeShaper deallocated"); } else { } else { throw new IllegalArgumentException("invalid configuration or operation: " + id); throw new IllegalArgumentException("invalid configuration or operation: " + id); } } Loading @@ -146,9 +193,9 @@ public final class VolumeShaper implements AutoCloseable { } } /** /** * Internal call to retrieve the current VolumeShaper state. * Internal call to retrieve the current {@code VolumeShaper} state. * @param id * @param id * @return the current {@vode VolumeShaper.State} * @return the current {@code VolumeShaper.State} * @throws IllegalStateException if the player has been deallocated or is uninitialized. * @throws IllegalStateException if the player has been deallocated or is uninitialized. */ */ private @NonNull VolumeShaper.State getStatePlayer(int id) { private @NonNull VolumeShaper.State getStatePlayer(int id) { Loading Loading @@ -180,6 +227,9 @@ public final class VolumeShaper implements AutoCloseable { * by {@link VolumeShaper#replace(Configuration, Operation, boolean) * by {@link VolumeShaper#replace(Configuration, Operation, boolean) * VolumeShaper.replace(Configuration, Operation, boolean)} * VolumeShaper.replace(Configuration, Operation, boolean)} * to replace an existing {@code configuration}. * to replace an existing {@code configuration}. * <p> * The {@link AudioTrack} and {@link MediaPlayer} classes implement * the {@link VolumeAutomation} interface. */ */ public static final class Configuration implements Parcelable { public static final class Configuration implements Parcelable { private static final int MAXIMUM_CURVE_POINTS = 16; private static final int MAXIMUM_CURVE_POINTS = 16; Loading Loading @@ -485,7 +535,7 @@ public final class VolumeShaper implements AutoCloseable { /** /** * @hide * @hide * Constructs a volume shaper from an id. * Constructs a {@code VolumeShaper} from an id. * * * This is an opaque handle for controlling a {@code VolumeShaper} that has * This is an opaque handle for controlling a {@code VolumeShaper} that has * already been sent to a player. The {@code id} is returned from the * already been sent to a player. The {@code id} is returned from the Loading Loading @@ -756,7 +806,7 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Sets the interpolator type. * Sets the interpolator type. * * * If omitted the interplator type is {@link #INTERPOLATOR_TYPE_CUBIC}. * If omitted the default interpolator type is {@link #INTERPOLATOR_TYPE_CUBIC}. * * * @param interpolatorType method of interpolation used for the volume curve. * @param interpolatorType method of interpolation used for the volume curve. * One of {@link #INTERPOLATOR_TYPE_STEP}, * One of {@link #INTERPOLATOR_TYPE_STEP}, Loading Loading @@ -802,7 +852,7 @@ public final class VolumeShaper implements AutoCloseable { } } /** /** * Sets the volume shaper duration in milliseconds. * Sets the {@code VolumeShaper} duration in milliseconds. * * * If omitted, the default duration is 1 second. * If omitted, the default duration is 1 second. * * Loading Loading @@ -1059,13 +1109,13 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Defer playback until next operation is sent. This is used * Defer playback until next operation is sent. This is used * when starting a VolumeShaper effect. * when starting a {@code VolumeShaper} effect. */ */ private static final int FLAG_DEFER = 1 << 3; private static final int FLAG_DEFER = 1 << 3; /** /** * Use the id specified in the configuration, creating * Use the id specified in the configuration, creating * VolumeShaper as needed; the configuration should be * {@code VolumeShaper} as needed; the configuration should be * TYPE_SCALE. * TYPE_SCALE. */ */ private static final int FLAG_CREATE_IF_NEEDED = 1 << 4; private static final int FLAG_CREATE_IF_NEEDED = 1 << 4; Loading @@ -1074,18 +1124,20 @@ public final class VolumeShaper implements AutoCloseable { private final int mFlags; private final int mFlags; private final int mReplaceId; private final int mReplaceId; private final float mXOffset; @Override @Override public String toString() { public String toString() { return "VolumeShaper.Operation{" return "VolumeShaper.Operation{" + "mFlags = 0x" + Integer.toHexString(mFlags).toUpperCase() + "mFlags = 0x" + Integer.toHexString(mFlags).toUpperCase() + ", mReplaceId = " + mReplaceId + ", mReplaceId = " + mReplaceId + ", mXOffset = " + mXOffset + "}"; + "}"; } } @Override @Override public int hashCode() { public int hashCode() { return Objects.hash(mFlags, mReplaceId); return Objects.hash(mFlags, mReplaceId, mXOffset); } } @Override @Override Loading @@ -1093,10 +1145,10 @@ public final class VolumeShaper implements AutoCloseable { if (!(o instanceof Operation)) return false; if (!(o instanceof Operation)) return false; if (o == this) return true; if (o == this) return true; final Operation other = (Operation) o; final Operation other = (Operation) o; // if xOffset (native field only) is brought into Java // we need to do proper NaN comparison as that is allowed. return mFlags == other.mFlags return mFlags == other.mFlags && mReplaceId == other.mReplaceId; && mReplaceId == other.mReplaceId && Float.compare(mXOffset, other.mXOffset) == 0; } } @Override @Override Loading @@ -1109,7 +1161,7 @@ public final class VolumeShaper implements AutoCloseable { // this needs to match the native VolumeShaper.Operation parceling // this needs to match the native VolumeShaper.Operation parceling dest.writeInt(mFlags); dest.writeInt(mFlags); dest.writeInt(mReplaceId); dest.writeInt(mReplaceId); dest.writeFloat(Float.NaN); // xOffset (ignored at Java level) dest.writeFloat(mXOffset); } } public static final Parcelable.Creator<VolumeShaper.Operation> CREATOR public static final Parcelable.Creator<VolumeShaper.Operation> CREATOR Loading @@ -1119,11 +1171,12 @@ public final class VolumeShaper implements AutoCloseable { // this needs to match the native VolumeShaper.Operation parceling // this needs to match the native VolumeShaper.Operation parceling final int flags = p.readInt(); final int flags = p.readInt(); final int replaceId = p.readInt(); final int replaceId = p.readInt(); final float xOffset = p.readFloat(); // ignored at Java level final float xOffset = p.readFloat(); return new VolumeShaper.Operation( return new VolumeShaper.Operation( flags flags , replaceId); , replaceId , xOffset); } } @Override @Override Loading @@ -1132,9 +1185,10 @@ public final class VolumeShaper implements AutoCloseable { } } }; }; private Operation(@Flag int flags, int replaceId) { private Operation(@Flag int flags, int replaceId, float xOffset) { mFlags = flags; mFlags = flags; mReplaceId = replaceId; mReplaceId = replaceId; mXOffset = xOffset; } } /** /** Loading @@ -1146,6 +1200,7 @@ public final class VolumeShaper implements AutoCloseable { public static final class Builder { public static final class Builder { int mFlags; int mFlags; int mReplaceId; int mReplaceId; float mXOffset; /** /** * Constructs a new {@code Builder} with the defaults. * Constructs a new {@code Builder} with the defaults. Loading @@ -1153,23 +1208,27 @@ public final class VolumeShaper implements AutoCloseable { public Builder() { public Builder() { mFlags = 0; mFlags = 0; mReplaceId = -1; mReplaceId = -1; mXOffset = Float.NaN; } } /** /** * Constructs a new Builder from a given {@code VolumeShaper.Operation} * Constructs a new {@code Builder} from a given {@code VolumeShaper.Operation} * @param operation the {@code VolumeShaper.operation} whose data will be * @param operation the {@code VolumeShaper.operation} whose data will be * reused in the new Builder. * reused in the new {@code Builder}. */ */ public Builder(@NonNull VolumeShaper.Operation operation) { public Builder(@NonNull VolumeShaper.Operation operation) { mReplaceId = operation.mReplaceId; mReplaceId = operation.mReplaceId; mFlags = operation.mFlags; mFlags = operation.mFlags; mXOffset = operation.mXOffset; } } /** /** * Replaces the previous {@code VolumeShaper} specified by id. * Replaces the previous {@code VolumeShaper} specified by {@code id}. * It has no other effect if the {@code VolumeShaper} is * * already expired. * The {@code VolumeShaper} specified by the {@code id} is removed * @param id the id of the previous {@code VolumeShaper}. * if it exists. The configuration should be TYPE_SCALE. * * @param id the {@code id} of the previous {@code VolumeShaper}. * @param join if true, match the volume of the previous * @param join if true, match the volume of the previous * shaper to the start volume of the new {@code VolumeShaper}. * shaper to the start volume of the new {@code VolumeShaper}. * @return the same {@code Builder} instance. * @return the same {@code Builder} instance. Loading @@ -1194,8 +1253,9 @@ public final class VolumeShaper implements AutoCloseable { } } /** /** * Terminates the VolumeShaper. * Terminates the {@code VolumeShaper}. * Do not call directly, use {@link VolumeShaper#release()}. * * Do not call directly, use {@link VolumeShaper#close()}. * @return the same {@code Builder} instance. * @return the same {@code Builder} instance. */ */ public @NonNull Builder terminate() { public @NonNull Builder terminate() { Loading @@ -1214,8 +1274,12 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Use the id specified in the configuration, creating * Use the id specified in the configuration, creating * VolumeShaper as needed; the configuration should be * {@code VolumeShaper} only as needed; the configuration should be * TYPE_SCALE. * TYPE_SCALE. * * If the {@code VolumeShaper} with the same id already exists * then the operation has no effect. * * @return the same {@code Builder} instance. * @return the same {@code Builder} instance. */ */ public @NonNull Builder createIfNeeded() { public @NonNull Builder createIfNeeded() { Loading @@ -1223,6 +1287,28 @@ public final class VolumeShaper implements AutoCloseable { return this; return this; } } /** * Sets the {@code xOffset} to use for the {@code VolumeShaper}. * * The {@code xOffset} is the position on the volume curve, * and setting takes effect when the {@code VolumeShaper} is used next. * * @param xOffset a value between (or equal to) 0.f and 1.f, or Float.NaN to ignore. * @return the same {@code Builder} instance. * @throws IllegalArgumentException if {@code xOffset} is not between 0.f and 1.f, * or a Float.NaN. */ public @NonNull Builder setXOffset(float xOffset) { if (xOffset < -0.f) { throw new IllegalArgumentException("Negative xOffset not allowed"); } else if (xOffset > 1.f) { throw new IllegalArgumentException("xOffset > 1.f not allowed"); } // Float.NaN passes through mXOffset = xOffset; return this; } /** /** * Sets the operation flag. Do not call this directly but one of the * Sets the operation flag. Do not call this directly but one of the * other builder methods. * other builder methods. Loading @@ -1245,7 +1331,7 @@ public final class VolumeShaper implements AutoCloseable { * @return a new {@code VolumeShaper.Operation} object * @return a new {@code VolumeShaper.Operation} object */ */ public @NonNull Operation build() { public @NonNull Operation build() { return new Operation(mFlags, mReplaceId); return new Operation(mFlags, mReplaceId, mXOffset); } } } // Operation.Builder } // Operation.Builder } // Operation } // Operation Loading Loading @@ -1316,15 +1402,18 @@ public final class VolumeShaper implements AutoCloseable { /** /** * Gets the volume of the {@link VolumeShaper.State}. * Gets the volume of the {@link VolumeShaper.State}. * @return linear volume between 0.f and 1.f. */ */ public float getVolume() { public float getVolume() { return mVolume; return mVolume; } } /** /** * Gets the elapsed ms of the {@link VolumeShaper.State} * Gets the {@code xOffset} position on the normalized curve * of the {@link VolumeShaper.State}. * @return the curve x position between 0.f and 1.f. */ */ public double getXOffset() { public float getXOffset() { return mXOffset; return mXOffset; } } } // State } // State Loading
media/jni/android_media_VolumeShaper.h +8 −3 Original line number Original line Diff line number Diff line Loading @@ -40,6 +40,7 @@ struct VolumeShaperHelper { jmethodID opConstructId; jmethodID opConstructId; jfieldID opFlagsId; jfieldID opFlagsId; jfieldID opReplaceIdId; jfieldID opReplaceIdId; jfieldID opXOffsetId; // VolumeShaper.State // VolumeShaper.State jclass stClazz; jclass stClazz; Loading Loading @@ -74,9 +75,10 @@ struct VolumeShaperHelper { if (opClazz == nullptr) { if (opClazz == nullptr) { return; return; } } opConstructId = env->GetMethodID(opClazz, "<init>", "(II)V"); opConstructId = env->GetMethodID(opClazz, "<init>", "(IIF)V"); opFlagsId = env->GetFieldID(opClazz, "mFlags", "I"); opFlagsId = env->GetFieldID(opClazz, "mFlags", "I"); opReplaceIdId = env->GetFieldID(opClazz, "mReplaceId", "I"); opReplaceIdId = env->GetFieldID(opClazz, "mReplaceId", "I"); opXOffsetId = env->GetFieldID(opClazz, "mXOffset", "F"); env->DeleteLocalRef(lclazz); env->DeleteLocalRef(lclazz); lclazz = env->FindClass("android/media/VolumeShaper$State"); lclazz = env->FindClass("android/media/VolumeShaper$State"); Loading Loading @@ -179,17 +181,20 @@ struct VolumeShaperHelper { VolumeShaper::Operation::Flag flags = VolumeShaper::Operation::Flag flags = (VolumeShaper::Operation::Flag)env->GetIntField(joperation, fields.opFlagsId); (VolumeShaper::Operation::Flag)env->GetIntField(joperation, fields.opFlagsId); int replaceId = env->GetIntField(joperation, fields.opReplaceIdId); int replaceId = env->GetIntField(joperation, fields.opReplaceIdId); float xOffset = env->GetFloatField(joperation, fields.opXOffsetId); sp<VolumeShaper::Operation> operation = new VolumeShaper::Operation(flags, replaceId); sp<VolumeShaper::Operation> operation = new VolumeShaper::Operation(flags, replaceId, xOffset); return operation; return operation; } } static jobject convertOperationToJobject( static jobject convertOperationToJobject( JNIEnv *env, const fields_t &fields, const sp<VolumeShaper::Operation> &operation) { JNIEnv *env, const fields_t &fields, const sp<VolumeShaper::Operation> &operation) { // prepare constructor args // prepare constructor args jvalue args[2]; jvalue args[3]; args[0].i = (jint)operation->getFlags(); args[0].i = (jint)operation->getFlags(); args[1].i = (jint)operation->getReplaceId(); args[1].i = (jint)operation->getReplaceId(); args[2].f = (jfloat)operation->getXOffset(); jobject joperation = env->NewObjectA(fields.opClazz, fields.opConstructId, args); jobject joperation = env->NewObjectA(fields.opClazz, fields.opConstructId, args); return joperation; return joperation; Loading