Merge "VolumeShaper: Enable xOffset from Java" into oc-dev

This commit is contained in:
Andy Hung
2017-05-18 16:15:44 +00:00
committed by Android (Google) Code Review
3 changed files with 133 additions and 39 deletions

View File

@@ -31,9 +31,9 @@ public interface VolumeAutomation {
* @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).
* @throws IllegalArgumentException if the {@code configuration} is not allowed by the player.
* @throws IllegalStateException if too many {@code VolumeShaper}s 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);

View File

@@ -31,8 +31,16 @@ import java.util.Objects;
/**
* The {@code VolumeShaper} class is used to automatically control audio volume during media
* 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.
*/
public final class VolumeShaper implements AutoCloseable {
@@ -52,7 +60,19 @@ public final class VolumeShaper implements AutoCloseable {
/**
* 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.
* @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) {
/* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation);
@@ -65,11 +85,24 @@ public final class VolumeShaper implements AutoCloseable {
* This allows the user to change the volume shape
* 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 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
* new {@code configuration} to the current volume of the existing
* {@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(
@NonNull Configuration configuration, @NonNull Operation operation, boolean join) {
@@ -81,7 +114,14 @@ public final class VolumeShaper implements AutoCloseable {
/**
* 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.
* @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() {
return getStatePlayer(mId).getVolume();
@@ -89,7 +129,14 @@ public final class VolumeShaper implements AutoCloseable {
/**
* 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
public void close() {
@@ -107,11 +154,11 @@ public final class VolumeShaper implements AutoCloseable {
@Override
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.
* @param configuration
* @param operation
@@ -137,7 +184,7 @@ public final class VolumeShaper implements AutoCloseable {
// Due to RPC handling, we translate integer codes to exceptions right before
// delivering to the user.
if (id == VOLUME_SHAPER_INVALID_OPERATION) {
throw new IllegalStateException("player or volume shaper deallocated");
throw new IllegalStateException("player or VolumeShaper deallocated");
} else {
throw new IllegalArgumentException("invalid configuration or operation: " + id);
}
@@ -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
* @return the current {@vode VolumeShaper.State}
* @return the current {@code VolumeShaper.State}
* @throws IllegalStateException if the player has been deallocated or is uninitialized.
*/
private @NonNull VolumeShaper.State getStatePlayer(int id) {
@@ -180,6 +227,9 @@ public final class VolumeShaper implements AutoCloseable {
* by {@link VolumeShaper#replace(Configuration, Operation, boolean)
* VolumeShaper.replace(Configuration, Operation, boolean)}
* 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 {
private static final int MAXIMUM_CURVE_POINTS = 16;
@@ -485,7 +535,7 @@ public final class VolumeShaper implements AutoCloseable {
/**
* @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
* already been sent to a player. The {@code id} is returned from the
@@ -756,7 +806,7 @@ public final class VolumeShaper implements AutoCloseable {
/**
* 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.
* One of {@link #INTERPOLATOR_TYPE_STEP},
@@ -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.
*
@@ -1059,13 +1109,13 @@ public final class VolumeShaper implements AutoCloseable {
/**
* 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;
/**
* 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.
*/
private static final int FLAG_CREATE_IF_NEEDED = 1 << 4;
@@ -1074,18 +1124,20 @@ public final class VolumeShaper implements AutoCloseable {
private final int mFlags;
private final int mReplaceId;
private final float mXOffset;
@Override
public String toString() {
return "VolumeShaper.Operation{"
+ "mFlags = 0x" + Integer.toHexString(mFlags).toUpperCase()
+ ", mReplaceId = " + mReplaceId
+ ", mXOffset = " + mXOffset
+ "}";
}
@Override
public int hashCode() {
return Objects.hash(mFlags, mReplaceId);
return Objects.hash(mFlags, mReplaceId, mXOffset);
}
@Override
@@ -1093,10 +1145,10 @@ public final class VolumeShaper implements AutoCloseable {
if (!(o instanceof Operation)) return false;
if (o == this) return true;
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
&& mReplaceId == other.mReplaceId;
&& mReplaceId == other.mReplaceId
&& Float.compare(mXOffset, other.mXOffset) == 0;
}
@Override
@@ -1109,7 +1161,7 @@ public final class VolumeShaper implements AutoCloseable {
// this needs to match the native VolumeShaper.Operation parceling
dest.writeInt(mFlags);
dest.writeInt(mReplaceId);
dest.writeFloat(Float.NaN); // xOffset (ignored at Java level)
dest.writeFloat(mXOffset);
}
public static final Parcelable.Creator<VolumeShaper.Operation> CREATOR
@@ -1119,11 +1171,12 @@ public final class VolumeShaper implements AutoCloseable {
// this needs to match the native VolumeShaper.Operation parceling
final int flags = 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(
flags
, replaceId);
, replaceId
, xOffset);
}
@Override
@@ -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;
mReplaceId = replaceId;
mXOffset = xOffset;
}
/**
@@ -1146,6 +1200,7 @@ public final class VolumeShaper implements AutoCloseable {
public static final class Builder {
int mFlags;
int mReplaceId;
float mXOffset;
/**
* Constructs a new {@code Builder} with the defaults.
@@ -1153,23 +1208,27 @@ public final class VolumeShaper implements AutoCloseable {
public Builder() {
mFlags = 0;
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
* reused in the new Builder.
* reused in the new {@code Builder}.
*/
public Builder(@NonNull VolumeShaper.Operation operation) {
mReplaceId = operation.mReplaceId;
mFlags = operation.mFlags;
mXOffset = operation.mXOffset;
}
/**
* Replaces the previous {@code VolumeShaper} specified by id.
* It has no other effect if the {@code VolumeShaper} is
* already expired.
* @param id the id of the previous {@code VolumeShaper}.
* Replaces the previous {@code VolumeShaper} specified by {@code id}.
*
* The {@code VolumeShaper} specified by the {@code id} is removed
* 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
* shaper to the start volume of the new {@code VolumeShaper}.
* @return the same {@code Builder} instance.
@@ -1194,8 +1253,9 @@ public final class VolumeShaper implements AutoCloseable {
}
/**
* Terminates the VolumeShaper.
* Do not call directly, use {@link VolumeShaper#release()}.
* Terminates the {@code VolumeShaper}.
*
* Do not call directly, use {@link VolumeShaper#close()}.
* @return the same {@code Builder} instance.
*/
public @NonNull Builder terminate() {
@@ -1214,8 +1274,12 @@ public final class VolumeShaper implements AutoCloseable {
/**
* 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.
*
* If the {@code VolumeShaper} with the same id already exists
* then the operation has no effect.
*
* @return the same {@code Builder} instance.
*/
public @NonNull Builder createIfNeeded() {
@@ -1223,6 +1287,28 @@ public final class VolumeShaper implements AutoCloseable {
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
* other builder methods.
@@ -1245,7 +1331,7 @@ public final class VolumeShaper implements AutoCloseable {
* @return a new {@code VolumeShaper.Operation} object
*/
public @NonNull Operation build() {
return new Operation(mFlags, mReplaceId);
return new Operation(mFlags, mReplaceId, mXOffset);
}
} // Operation.Builder
} // Operation
@@ -1316,15 +1402,18 @@ public final class VolumeShaper implements AutoCloseable {
/**
* Gets the volume of the {@link VolumeShaper.State}.
* @return linear volume between 0.f and 1.f.
*/
public float getVolume() {
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;
}
} // State

View File

@@ -40,6 +40,7 @@ struct VolumeShaperHelper {
jmethodID opConstructId;
jfieldID opFlagsId;
jfieldID opReplaceIdId;
jfieldID opXOffsetId;
// VolumeShaper.State
jclass stClazz;
@@ -74,9 +75,10 @@ struct VolumeShaperHelper {
if (opClazz == nullptr) {
return;
}
opConstructId = env->GetMethodID(opClazz, "<init>", "(II)V");
opConstructId = env->GetMethodID(opClazz, "<init>", "(IIF)V");
opFlagsId = env->GetFieldID(opClazz, "mFlags", "I");
opReplaceIdId = env->GetFieldID(opClazz, "mReplaceId", "I");
opXOffsetId = env->GetFieldID(opClazz, "mXOffset", "F");
env->DeleteLocalRef(lclazz);
lclazz = env->FindClass("android/media/VolumeShaper$State");
@@ -179,17 +181,20 @@ struct VolumeShaperHelper {
VolumeShaper::Operation::Flag flags =
(VolumeShaper::Operation::Flag)env->GetIntField(joperation, fields.opFlagsId);
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;
}
static jobject convertOperationToJobject(
JNIEnv *env, const fields_t &fields, const sp<VolumeShaper::Operation> &operation) {
// prepare constructor args
jvalue args[2];
jvalue args[3];
args[0].i = (jint)operation->getFlags();
args[1].i = (jint)operation->getReplaceId();
args[2].f = (jfloat)operation->getXOffset();
jobject joperation = env->NewObjectA(fields.opClazz, fields.opConstructId, args);
return joperation;