blob: 98196c8b553701d2fdf8aceed483e58e4b8cfed3 [file] [log] [blame]
/*
* 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.cts;
import static org.testng.Assert.assertThrows;
import android.media.cts.R;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.MediaPlayer;
import android.media.VolumeShaper;
import android.os.Parcel;
import android.os.PowerManager;
import android.platform.test.annotations.AppModeFull;
import android.support.test.filters.FlakyTest;
import android.support.test.filters.LargeTest;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SmallTest;
import android.util.Log;
import com.android.compatibility.common.util.CtsAndroidTestCase;
import java.lang.AutoCloseable;
import java.util.Arrays;
/**
* VolumeShaperTest is automated using VolumeShaper.getVolume() to verify that a ramp
* or a duck is at the expected volume level. Listening to some tests is also possible,
* as we logcat the expected volume change.
*
* To see the listening messages:
*
* adb logcat | grep VolumeShaperTest
*/
@AppModeFull(reason = "TODO: evaluate and port to instant")
public class VolumeShaperTest extends CtsAndroidTestCase {
private static final String TAG = "VolumeShaperTest";
// ramp or duck time (duration) used in tests.
private static final long RAMP_TIME_MS = 3000;
// volume tolerance for completion volume checks.
private static final float VOLUME_TOLERANCE = 0.0000001f;
// volume difference permitted on replace() with join.
private static final float JOIN_VOLUME_TOLERANCE = 0.1f;
// time to wait for player state change
private static final long WARMUP_TIME_MS = 300;
private static final VolumeShaper.Configuration SILENCE =
new VolumeShaper.Configuration.Builder()
.setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
.setCurve(new float[] { 0.f, 1.f } /* times */,
new float[] { 0.f, 0.f } /* volumes */)
.setDuration(RAMP_TIME_MS)
.build();
// Duck configurations go from 1.f down to 0.2f (not full ramp down).
private static final VolumeShaper.Configuration LINEAR_DUCK =
new VolumeShaper.Configuration.Builder()
.setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
.setCurve(new float[] { 0.f, 1.f } /* times */,
new float[] { 1.f, 0.2f } /* volumes */)
.setDuration(RAMP_TIME_MS)
.build();
// Ramp configurations go from 0.f up to 1.f
private static final VolumeShaper.Configuration LINEAR_RAMP =
new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP)
.setDuration(RAMP_TIME_MS)
.build();
private static final VolumeShaper.Configuration CUBIC_RAMP =
new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.CUBIC_RAMP)
.setDuration(RAMP_TIME_MS)
.build();
private static final VolumeShaper.Configuration SINE_RAMP =
new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.SINE_RAMP)
.setDuration(RAMP_TIME_MS)
.build();
private static final VolumeShaper.Configuration SCURVE_RAMP =
new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.SCURVE_RAMP)
.setDuration(RAMP_TIME_MS)
.build();
// internal use only
private static final VolumeShaper.Configuration LOG_RAMP =
new VolumeShaper.Configuration.Builder()
.setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
.setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_VOLUME_IN_DBFS)
.setCurve(new float[] { 0.f, 1.f } /* times */,
new float[] { -80.f, 0.f } /* volumes */)
.setDuration(RAMP_TIME_MS)
.build();
// a step ramp is not continuous, so we have a different test for it.
private static final VolumeShaper.Configuration STEP_RAMP =
new VolumeShaper.Configuration.Builder()
.setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_STEP)
.setCurve(new float[] { 0.f, 1.f } /* times */,
new float[] { 0.f, 1.f } /* volumes */)
.setDuration(RAMP_TIME_MS)
.build();
private static final VolumeShaper.Configuration[] ALL_STANDARD_RAMPS = {
LINEAR_RAMP,
CUBIC_RAMP,
SINE_RAMP,
SCURVE_RAMP,
};
private static final VolumeShaper.Configuration[] TEST_DUCKS = {
LINEAR_DUCK,
};
// this ramp should result in non-monotonic behavior with a typical cubic spline.
private static final VolumeShaper.Configuration MONOTONIC_TEST =
new VolumeShaper.Configuration.Builder()
.setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC_MONOTONIC)
.setCurve(new float[] { 0.f, 0.3f, 0.7f, 1.f } /* times */,
new float[] { 0.f, 0.5f, 0.5f, 1.f } /* volumes */)
.setDuration(RAMP_TIME_MS)
.build();
private static final VolumeShaper.Configuration MONOTONIC_TEST_FAIL =
new VolumeShaper.Configuration.Builder(MONOTONIC_TEST)
.setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC)
.build();
private static final VolumeShaper.Operation[] ALL_STANDARD_OPERATIONS = {
VolumeShaper.Operation.PLAY,
VolumeShaper.Operation.REVERSE,
};
private boolean hasAudioOutput() {
return getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
}
private boolean isLowRamDevice() {
return ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE))
.isLowRamDevice();
}
private static AudioTrack createSineAudioTrack() {
final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT;
final int TEST_MODE = AudioTrack.MODE_STATIC;
final int TEST_SR = 48000;
final AudioFormat format = new AudioFormat.Builder()
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.setEncoding(TEST_FORMAT)
.setSampleRate(TEST_SR)
.build();
final int frameCount = AudioHelper.frameCountFromMsec(100 /*ms*/, format);
final int frameSize = AudioHelper.frameSizeFromFormat(format);
final AudioTrack audioTrack = new AudioTrack.Builder()
.setAudioFormat(format)
.setBufferSizeInBytes(frameCount * frameSize)
.setTransferMode(TEST_MODE)
.build();
// create float array and write it
final int sampleCount = frameCount * format.getChannelCount();
final float[] vaf = AudioHelper.createSoundDataInFloatArray(
sampleCount, TEST_SR,
600 * format.getChannelCount() /* frequency */, 0 /* sweep */);
assertEquals(vaf.length, audioTrack.write(vaf, 0 /* offsetInFloats */, vaf.length,
AudioTrack.WRITE_NON_BLOCKING));
audioTrack.setLoopPoints(0, frameCount, -1 /* loopCount */);
return audioTrack;
}
private MediaPlayer createMediaPlayer(boolean offloaded) {
// MP3 resource should be greater than 1m to introduce offloading
final int RESOURCE_ID = R.raw.test1m1s;
final MediaPlayer mediaPlayer = MediaPlayer.create(getContext(),
RESOURCE_ID,
new AudioAttributes.Builder()
.setUsage(offloaded ?
AudioAttributes.USAGE_MEDIA // offload allowed
: AudioAttributes.USAGE_NOTIFICATION) // offload not allowed
.build(),
new AudioManager(getContext()).generateAudioSessionId());
mediaPlayer.setWakeMode(getContext(), PowerManager.PARTIAL_WAKE_LOCK);
mediaPlayer.setLooping(true);
return mediaPlayer;
}
private static void checkEqual(String testName,
VolumeShaper.Configuration expected, VolumeShaper.Configuration actual) {
assertEquals(testName + " configuration should be equal",
expected, actual);
assertEquals(testName + " configuration.hashCode() should be equal",
expected.hashCode(), actual.hashCode());
assertEquals(testName + " configuration.toString() should be equal",
expected.toString(), actual.toString());
}
private static void checkNotEqual(String testName,
VolumeShaper.Configuration notEqual, VolumeShaper.Configuration actual) {
assertTrue(testName + " configuration should not be equal",
!actual.equals(notEqual));
assertTrue(testName + " configuration.hashCode() should not be equal",
actual.hashCode() != notEqual.hashCode());
assertTrue(testName + " configuration.toString() should not be equal",
!actual.toString().equals(notEqual.toString()));
}
// generic player class to simplify testing
private interface Player extends AutoCloseable {
public void start();
public void pause();
public void stop();
@Override public void close();
public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration);
public String name();
}
private static class AudioTrackPlayer implements Player {
public AudioTrackPlayer() {
mTrack = createSineAudioTrack();
mName = new String("AudioTrack");
}
@Override public void start() {
mTrack.play();
}
@Override public void pause() {
mTrack.pause();
}
@Override public void stop() {
mTrack.stop();
}
@Override public void close() {
mTrack.release();
}
@Override
public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration) {
return mTrack.createVolumeShaper(configuration);
}
@Override public String name() {
return mName;
}
private final AudioTrack mTrack;
private final String mName;
}
private class MediaPlayerPlayer implements Player {
public MediaPlayerPlayer(boolean offloaded) {
mPlayer = createMediaPlayer(offloaded);
mName = new String("MediaPlayer" + (offloaded ? "Offloaded" : "NonOffloaded"));
}
@Override public void start() {
mPlayer.start();
}
@Override public void pause() {
mPlayer.pause();
}
@Override public void stop() {
mPlayer.stop();
}
@Override public void close() {
mPlayer.release();
}
@Override
public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration) {
return mPlayer.createVolumeShaper(configuration);
}
@Override public String name() {
return mName;
}
private final MediaPlayer mPlayer;
private final String mName;
}
private static final int PLAYER_TYPES = 3;
private static final int PLAYER_TYPE_AUDIO_TRACK = 0;
private static final int PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED = 1;
private static final int PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED = 2;
private Player createPlayer(int type) {
switch (type) {
case PLAYER_TYPE_AUDIO_TRACK:
return new AudioTrackPlayer();
case PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED:
return new MediaPlayerPlayer(false /* offloaded */);
case PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED:
return new MediaPlayerPlayer(true /* offloaded */);
default:
return null;
}
}
private static void testBuildRamp(int points) {
float[] ramp = new float[points];
final float fscale = 1.f / (points - 1);
for (int i = 0; i < points; ++i) {
ramp[i] = i * fscale;
}
ramp[points - 1] = 1.f;
// does it build?
final VolumeShaper.Configuration config = new VolumeShaper.Configuration.Builder()
.setCurve(ramp, ramp)
.build();
}
@SmallTest
public void testVolumeShaperConfigurationBuilder() throws Exception {
final String TEST_NAME = "testVolumeShaperConfigurationBuilder";
// Verify that IllegalStateExceptions are properly triggered
// for methods with no arguments.
Log.d(TAG, TEST_NAME + " configuration builder should throw ISE if no curve specified");
assertThrows(IllegalStateException.class,
new VolumeShaper.Configuration.Builder()
::build);
assertThrows(IllegalStateException.class,
new VolumeShaper.Configuration.Builder()
::invertVolumes);
assertThrows(IllegalStateException.class,
new VolumeShaper.Configuration.Builder()
::reflectTimes);
Log.d(TAG, TEST_NAME + " configuration builder should IAE on invalid curve");
// Verify IllegalArgumentExceptions are properly triggered
// for methods with arguments.
final float[] ohOne = { 0.f, 1.f };
final float[][] invalidCurves = {
{ -1.f, 1.f },
{ 0.5f },
{ 0.f, 2.f },
};
for (float[] invalidCurve : invalidCurves) {
assertThrows(IllegalArgumentException.class,
() -> {
new VolumeShaper.Configuration.Builder()
.setCurve(invalidCurve, ohOne)
.build(); });
assertThrows(IllegalArgumentException.class,
() -> {
new VolumeShaper.Configuration.Builder()
.setCurve(ohOne, invalidCurve)
.build(); });
}
Log.d(TAG, TEST_NAME + " configuration builder should throw IAE on invalid duration");
assertThrows(IllegalArgumentException.class,
() -> {
new VolumeShaper.Configuration.Builder()
.setCurve(ohOne, ohOne)
.setDuration(-1)
.build(); });
Log.d(TAG, TEST_NAME + " configuration builder should throw IAE on invalid interpolator");
assertThrows(IllegalArgumentException.class,
() -> {
new VolumeShaper.Configuration.Builder()
.setCurve(ohOne, ohOne)
.setInterpolatorType(-1)
.build(); });
// Verify defaults.
// Use the Builder with setCurve(ohOne, ohOne).
final VolumeShaper.Configuration config =
new VolumeShaper.Configuration.Builder().setCurve(ohOne, ohOne).build();
assertEquals(TEST_NAME + " default interpolation should be cubic",
VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC, config.getInterpolatorType());
assertEquals(TEST_NAME + " default duration should be 1000 ms",
1000, config.getDuration());
assertTrue(TEST_NAME + " times should be { 0.f, 1.f }",
Arrays.equals(ohOne, config.getTimes()));
assertTrue(TEST_NAME + " volumes should be { 0.f, 1.f }",
Arrays.equals(ohOne, config.getVolumes()));
// Due to precision problems, we cannot have ramps that do not have
// perfect binary representation for equality comparison.
// (For example, 0.1 is a repeating mantissa in binary,
// but 0.25, 0.5 can be expressed with few mantissa bits).
final float[] binaryCurve1 = { 0.f, 0.25f, 0.5f, 0.625f, 1.f };
final float[] binaryCurve2 = { 0.f, 0.125f, 0.375f, 0.75f, 1.f };
final VolumeShaper.Configuration[] BINARY_RAMPS = {
LINEAR_RAMP,
CUBIC_RAMP,
new VolumeShaper.Configuration.Builder()
.setCurve(binaryCurve1, binaryCurve2)
.build(),
};
// Verify volume inversion and time reflection work as expected
// with ramps (which start at { 0.f, 0.f } and end at { 1.f, 1.f }).
for (VolumeShaper.Configuration testRamp : BINARY_RAMPS) {
VolumeShaper.Configuration ramp;
ramp = new VolumeShaper.Configuration.Builder(testRamp).build();
checkEqual(TEST_NAME, testRamp, ramp);
ramp = new VolumeShaper.Configuration.Builder(testRamp)
.setDuration(10)
.build();
checkNotEqual(TEST_NAME, testRamp, ramp);
ramp = new VolumeShaper.Configuration.Builder(testRamp).build();
checkEqual(TEST_NAME, testRamp, ramp);
ramp = new VolumeShaper.Configuration.Builder(testRamp)
.invertVolumes()
.build();
checkNotEqual(TEST_NAME, testRamp, ramp);
ramp = new VolumeShaper.Configuration.Builder(testRamp)
.invertVolumes()
.invertVolumes()
.build();
checkEqual(TEST_NAME, testRamp, ramp);
ramp = new VolumeShaper.Configuration.Builder(testRamp)
.reflectTimes()
.build();
checkNotEqual(TEST_NAME, testRamp, ramp);
ramp = new VolumeShaper.Configuration.Builder(testRamp)
.reflectTimes()
.reflectTimes()
.build();
checkEqual(TEST_NAME, testRamp, ramp);
// check scaling start and end volumes
ramp = new VolumeShaper.Configuration.Builder(testRamp)
.scaleToStartVolume(0.5f)
.build();
checkNotEqual(TEST_NAME, testRamp, ramp);
ramp = new VolumeShaper.Configuration.Builder(testRamp)
.scaleToStartVolume(0.5f)
.scaleToStartVolume(0.f)
.build();
checkEqual(TEST_NAME, testRamp, ramp);
ramp = new VolumeShaper.Configuration.Builder(testRamp)
.scaleToStartVolume(0.5f)
.scaleToEndVolume(0.f)
.scaleToStartVolume(1.f)
.invertVolumes()
.build();
checkEqual(TEST_NAME, testRamp, ramp);
}
// check that getMaximumCurvePoints() returns the correct value
final int maxPoints = VolumeShaper.Configuration.getMaximumCurvePoints();
testBuildRamp(maxPoints); // no exceptions here.
if (maxPoints < Integer.MAX_VALUE) {
Log.d(TAG, TEST_NAME + " configuration builder "
+ "should throw IAE if getMaximumCurvePoints() exceeded");
assertThrows(IllegalArgumentException.class,
() -> { testBuildRamp(maxPoints + 1); });
}
} // testVolumeShaperConfigurationBuilder
@SmallTest
public void testVolumeShaperConfigurationParcelable() throws Exception {
final String TEST_NAME = "testVolumeShaperConfigurationParcelable";
for (VolumeShaper.Configuration config : ALL_STANDARD_RAMPS) {
assertEquals(TEST_NAME + " no parceled file descriptors",
0 /* expected */, config.describeContents());
final Parcel srcParcel = Parcel.obtain();
config.writeToParcel(srcParcel, 0 /* flags */);
final byte[] marshallBuffer = srcParcel.marshall();
final Parcel dstParcel = Parcel.obtain();
dstParcel.unmarshall(marshallBuffer, 0 /* offset */, marshallBuffer.length);
dstParcel.setDataPosition(0);
final VolumeShaper.Configuration restoredConfig =
VolumeShaper.Configuration.CREATOR.createFromParcel(dstParcel);
assertEquals(TEST_NAME +
" marshalled/restored VolumeShaper.Configuration should match",
config, restoredConfig);
}
} // testVolumeShaperConfigurationParcelable
@SmallTest
public void testVolumeShaperOperationParcelable() throws Exception {
final String TEST_NAME = "testVolumeShaperOperationParcelable";
for (VolumeShaper.Operation operation : ALL_STANDARD_OPERATIONS) {
assertEquals(TEST_NAME + " no parceled file descriptors",
0 /* expected */, operation.describeContents());
final Parcel srcParcel = Parcel.obtain();
operation.writeToParcel(srcParcel, 0 /* flags */);
final byte[] marshallBuffer = srcParcel.marshall();
final Parcel dstParcel = Parcel.obtain();
dstParcel.unmarshall(marshallBuffer, 0 /* offset */, marshallBuffer.length);
dstParcel.setDataPosition(0);
final VolumeShaper.Operation restoredOperation =
VolumeShaper.Operation.CREATOR.createFromParcel(dstParcel);
assertEquals(TEST_NAME +
" marshalled/restored VolumeShaper.Operation should match",
operation, restoredOperation);
}
} // testVolumeShaperOperationParcelable
// This tests that we can't create infinite shapers and cause audioserver
// to crash due to memory or performance issues. Typically around 16 app based
// shapers are allowed by the audio server.
@SmallTest
public void testMaximumShapers() {
final String TEST_NAME = "testMaximumShapers";
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
final int WAY_TOO_MANY_SHAPERS = 1000;
for (int p = 0; p < PLAYER_TYPES; ++p) {
try (Player player = createPlayer(p)) {
final String testName = TEST_NAME + " " + player.name();
final VolumeShaper[] shapers = new VolumeShaper[WAY_TOO_MANY_SHAPERS];
int i = 0;
try {
for (; i < shapers.length; ++i) {
shapers[i] = player.createVolumeShaper(SILENCE);
}
fail(testName + " should not be able to create "
+ shapers.length + " shapers");
} catch (IllegalStateException ise) {
Log.d(TAG, testName + " " + i + " shapers created before failure (OK)");
}
}
// volume shapers close when player closes.
}
} // testMaximumShapers
@LargeTest
public void testPlayerDuck() throws Exception {
final String TEST_NAME = "testPlayerDuck";
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
for (int p = 0; p < PLAYER_TYPES; ++p) {
try ( Player player = createPlayer(p);
VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
) {
final String testName = TEST_NAME + " " + player.name();
Log.d(TAG, testName + " starting");
player.start();
Thread.sleep(WARMUP_TIME_MS);
runDuckTest(testName, volumeShaper);
runCloseTest(testName, volumeShaper);
}
}
} // testPlayerDuck
@LargeTest
public void testPlayerRamp() throws Exception {
final String TEST_NAME = "testPlayerRamp";
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
for (int p = 0; p < PLAYER_TYPES; ++p) {
try ( Player player = createPlayer(p);
VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
) {
final String testName = TEST_NAME + " " + player.name();
Log.d(TAG, testName + " starting");
player.start();
Thread.sleep(WARMUP_TIME_MS);
runRampTest(testName, volumeShaper);
runCloseTest(testName, volumeShaper);
}
}
} // testPlayerRamp
@LargeTest
public void testPlayerCornerCase() throws Exception {
final String TEST_NAME = "testPlayerCornerCase";
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
final VolumeShaper.Configuration config = LINEAR_RAMP;
for (int p = 0; p < PLAYER_TYPES; ++p) {
Player player = null;
VolumeShaper volumeShaper = null;
try {
player = createPlayer(p);
volumeShaper = player.createVolumeShaper(config);
final String testName = TEST_NAME + " " + player.name();
runStartIdleTest(testName, volumeShaper, player);
runRampCornerCaseTest(testName, volumeShaper, config);
runCloseTest(testName, volumeShaper);
Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
volumeShaper = player.createVolumeShaper(config);
player.pause();
Thread.sleep(100 /* millis */);
runStartIdleTest(testName, volumeShaper, player);
// volumeShaper not explicitly closed, will close upon finalize or player close.
Log.d(TAG, testName + " recreating VolumeShaper and repeating with stop");
volumeShaper = player.createVolumeShaper(config);
player.stop();
Thread.sleep(100 /* millis */);
runStartIdleTest(testName, volumeShaper, player);
Log.d(TAG, testName + " closing Player before VolumeShaper");
player.close();
runCloseTest2(testName, volumeShaper);
} finally {
if (volumeShaper != null) {
volumeShaper.close();
}
if (player != null) {
player.close();
}
}
}
} // testPlayerCornerCase
@LargeTest
public void testPlayerCornerCase2() throws Exception {
final String TEST_NAME = "testPlayerCornerCase2";
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
final VolumeShaper.Configuration config = LINEAR_RAMP;
for (int p = 0; p < PLAYER_TYPES; ++p) {
Player player = null;
VolumeShaper volumeShaper = null;
try {
player = createPlayer(p);
volumeShaper = player.createVolumeShaper(config);
final String testName = TEST_NAME + " " + player.name();
runStartSyncTest(testName, volumeShaper, player);
runCloseTest(testName, volumeShaper);
Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
volumeShaper = player.createVolumeShaper(config);
player.pause();
Thread.sleep(100 /* millis */);
runStartSyncTest(testName, volumeShaper, player);
Log.d(TAG, testName + " closing Player before VolumeShaper");
player.close();
runCloseTest2(testName, volumeShaper);
} finally {
if (volumeShaper != null) {
volumeShaper.close();
}
if (player != null) {
player.close();
}
}
}
} // testPlayerCornerCase2
@FlakyTest
@LargeTest
public void testPlayerJoin() throws Exception {
final String TEST_NAME = "testPlayerJoin";
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
for (int p = 0; p < PLAYER_TYPES; ++p) {
try ( Player player = createPlayer(p);
VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
) {
final String testName = TEST_NAME + " " + player.name();
volumeShaper.apply(VolumeShaper.Operation.PLAY);
player.start();
Thread.sleep(WARMUP_TIME_MS);
Log.d(TAG, " we join several LINEAR_RAMPS together "
+ " this effectively is one LINEAR_RAMP (volume increasing).");
final long durationMs = 10000;
final long incrementMs = 1000;
for (long i = 0; i < durationMs; i += incrementMs) {
Log.d(TAG, testName + " Play - join " + i);
volumeShaper.replace(new VolumeShaper.Configuration.Builder(LINEAR_RAMP)
.setDuration(durationMs - i)
.build(),
VolumeShaper.Operation.PLAY, true /* join */);
assertEquals(testName + " linear ramp should continue on join",
(float)i / durationMs, volumeShaper.getVolume(), 0.05 /* epsilon */);
Thread.sleep(incrementMs);
}
Log.d(TAG, testName + "volume at max level now (closing player)");
}
}
} // testPlayerJoin
@LargeTest
public void testPlayerCubicMonotonic() throws Exception {
final String TEST_NAME = "testPlayerCubicMonotonic";
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
final VolumeShaper.Configuration configurations[] =
new VolumeShaper.Configuration[] {
MONOTONIC_TEST,
CUBIC_RAMP,
SCURVE_RAMP,
SINE_RAMP,
};
for (int p = 0; p < PLAYER_TYPES; ++p) {
try ( Player player = createPlayer(p);
VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
) {
final String testName = TEST_NAME + " " + player.name();
volumeShaper.apply(VolumeShaper.Operation.PLAY);
player.start();
Thread.sleep(WARMUP_TIME_MS);
for (VolumeShaper.Configuration configuration : configurations) {
// test configurations known monotonic
Log.d(TAG, testName + " starting test");
float lastVolume = 0;
final long incrementMs = 100;
volumeShaper.replace(configuration,
VolumeShaper.Operation.PLAY, true /* join */);
// monotonicity test
for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
final float volume = volumeShaper.getVolume();
assertTrue(testName + " montonic volume should increase "
+ volume + " >= " + lastVolume,
(volume >= lastVolume));
lastVolume = volume;
Thread.sleep(incrementMs);
}
Thread.sleep(WARMUP_TIME_MS);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " final monotonic value should be 1.f, but is " + lastVolume,
1.f, lastVolume, VOLUME_TOLERANCE);
Log.d(TAG, "invert");
// invert
VolumeShaper.Configuration newConfiguration =
new VolumeShaper.Configuration.Builder(configuration)
.invertVolumes()
.build();
volumeShaper.replace(newConfiguration,
VolumeShaper.Operation.PLAY, true /* join */);
// monotonicity test
for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
final float volume = volumeShaper.getVolume();
assertTrue(testName + " montonic volume should decrease "
+ volume + " <= " + lastVolume,
(volume <= lastVolume));
lastVolume = volume;
Thread.sleep(incrementMs);
}
Thread.sleep(WARMUP_TIME_MS);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " final monotonic value should be 0.f, but is " + lastVolume,
0.f, lastVolume, VOLUME_TOLERANCE);
// invert + reflect
Log.d(TAG, "invert and reflect");
newConfiguration =
new VolumeShaper.Configuration.Builder(configuration)
.invertVolumes()
.reflectTimes()
.build();
volumeShaper.replace(newConfiguration,
VolumeShaper.Operation.PLAY, true /* join */);
// monotonicity test
for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
final float volume = volumeShaper.getVolume();
assertTrue(testName + " montonic volume should increase "
+ volume + " >= " + lastVolume,
(volume >= lastVolume - VOLUME_TOLERANCE));
lastVolume = volume;
Thread.sleep(incrementMs);
}
Thread.sleep(WARMUP_TIME_MS);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " final monotonic value should be 1.f, but is " + lastVolume,
1.f, lastVolume, VOLUME_TOLERANCE);
// reflect
Log.d(TAG, "reflect");
newConfiguration =
new VolumeShaper.Configuration.Builder(configuration)
.reflectTimes()
.build();
volumeShaper.replace(newConfiguration,
VolumeShaper.Operation.PLAY, true /* join */);
// monotonicity test
for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
final float volume = volumeShaper.getVolume();
assertTrue(testName + " montonic volume should decrease "
+ volume + " <= " + lastVolume,
(volume <= lastVolume));
lastVolume = volume;
Thread.sleep(incrementMs);
}
Thread.sleep(WARMUP_TIME_MS);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " final monotonic value should be 0.f, but is " + lastVolume,
0.f, lastVolume, VOLUME_TOLERANCE);
}
}
}
} // testPlayerCubicMonotonic
@LargeTest
public void testPlayerStepRamp() throws Exception {
final String TEST_NAME = "testPlayerStepRamp";
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
// We test that the step ramp persists on value until the next control point.
// The STEP_RAMP has only 2 control points (at time 0.f and at 1.f).
// It should suddenly jump to full volume at 1.f (full duration).
// Note: invertVolumes() and reflectTimes() are not symmetric for STEP interpolation;
// however, VolumeShaper.Operation.REVERSE will behave symmetrically.
for (int p = 0; p < PLAYER_TYPES; ++p) {
try ( Player player = createPlayer(p);
VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
) {
final String testName = TEST_NAME + " " + player.name();
volumeShaper.apply(VolumeShaper.Operation.PLAY);
player.start();
Thread.sleep(WARMUP_TIME_MS);
final VolumeShaper.Configuration configuration = STEP_RAMP;
Log.d(TAG, testName + " starting test (sudden jump to full after "
+ RAMP_TIME_MS + " milliseconds)");
volumeShaper.replace(configuration,
VolumeShaper.Operation.PLAY, true /* join */);
Thread.sleep(RAMP_TIME_MS / 2);
float lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " middle value should be 0.f, but is " + lastVolume,
0.f, lastVolume, VOLUME_TOLERANCE);
Thread.sleep(RAMP_TIME_MS / 2 + 1000);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " final value should be 1.f, but is " + lastVolume,
1.f, lastVolume, VOLUME_TOLERANCE);
Log.d(TAG, "invert (sudden jump to silence after "
+ RAMP_TIME_MS + " milliseconds)");
// invert
VolumeShaper.Configuration newConfiguration =
new VolumeShaper.Configuration.Builder(configuration)
.invertVolumes()
.build();
volumeShaper.replace(newConfiguration,
VolumeShaper.Operation.PLAY, true /* join */);
Thread.sleep(RAMP_TIME_MS / 2);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " middle value should be 1.f, but is " + lastVolume,
1.f, lastVolume, VOLUME_TOLERANCE);
Thread.sleep(RAMP_TIME_MS / 2 + 1000);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " final value should be 0.f, but is " + lastVolume,
0.f, lastVolume, VOLUME_TOLERANCE);
// invert + reflect
Log.d(TAG, "invert and reflect (sudden jump to full after "
+ RAMP_TIME_MS + " milliseconds)");
newConfiguration =
new VolumeShaper.Configuration.Builder(configuration)
.invertVolumes()
.reflectTimes()
.build();
volumeShaper.replace(newConfiguration,
VolumeShaper.Operation.PLAY, true /* join */);
Thread.sleep(RAMP_TIME_MS / 2);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " middle value should be 0.f, but is " + lastVolume,
0.f, lastVolume, VOLUME_TOLERANCE);
Thread.sleep(RAMP_TIME_MS / 2 + 1000);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " final value should be 1.f, but is " + lastVolume,
1.f, lastVolume, VOLUME_TOLERANCE);
// reflect
Log.d(TAG, "reflect (sudden jump to silence after "
+ RAMP_TIME_MS + " milliseconds)");
newConfiguration =
new VolumeShaper.Configuration.Builder(configuration)
.reflectTimes()
.build();
volumeShaper.replace(newConfiguration,
VolumeShaper.Operation.PLAY, true /* join */);
Thread.sleep(RAMP_TIME_MS / 2);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " middle value should be 1.f, but is " + lastVolume,
1.f, lastVolume, VOLUME_TOLERANCE);
Thread.sleep(RAMP_TIME_MS / 2 + 1000);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " final value should be 0.f, but is " + lastVolume,
0.f, lastVolume, VOLUME_TOLERANCE);
Log.d(TAG, "reverse (immediate jump to full)");
volumeShaper.apply(VolumeShaper.Operation.REVERSE);
Thread.sleep(RAMP_TIME_MS / 2);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " middle value should be 1.f, but is " + lastVolume,
1.f, lastVolume, VOLUME_TOLERANCE);
Thread.sleep(RAMP_TIME_MS / 2 + 1000);
lastVolume = volumeShaper.getVolume();
assertEquals(testName
+ " final value should be 1.f, but is " + lastVolume,
1.f, lastVolume, VOLUME_TOLERANCE);
}
}
} // testPlayerStepRamp
@LargeTest
public void testPlayerTwoShapers() throws Exception {
final String TEST_NAME = "testPlayerTwoShapers";
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
final long durationMs = 10000;
// Ramp configurations go from 0.f up to 1.f, Duck from 1.f to 0.f
// With the two ramps combined, the audio should rise and then fall.
final VolumeShaper.Configuration LONG_RAMP =
new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP)
.setDuration(durationMs)
.build();
final VolumeShaper.Configuration LONG_DUCK =
new VolumeShaper.Configuration.Builder(LONG_RAMP)
.reflectTimes()
.build();
for (int p = 0; p < PLAYER_TYPES; ++p) {
try ( Player player = createPlayer(p);
VolumeShaper volumeShaperRamp = player.createVolumeShaper(LONG_RAMP);
VolumeShaper volumeShaperDuck = player.createVolumeShaper(LONG_DUCK);
) {
final String testName = TEST_NAME + " " + player.name();
final float firstVolumeRamp = volumeShaperRamp.getVolume();
final float firstVolumeDuck = volumeShaperDuck.getVolume();
assertEquals(testName
+ " first ramp value should be 0.f, but is " + firstVolumeRamp,
0.f, firstVolumeRamp, VOLUME_TOLERANCE);
assertEquals(testName
+ " first duck value should be 1.f, but is " + firstVolumeDuck,
1.f, firstVolumeDuck, VOLUME_TOLERANCE);
player.start();
Thread.sleep(1000);
final float lastVolumeRamp = volumeShaperRamp.getVolume();
final float lastVolumeDuck = volumeShaperDuck.getVolume();
assertEquals(testName
+ " no-play ramp value should be 0.f, but is " + lastVolumeRamp,
0.f, lastVolumeRamp, VOLUME_TOLERANCE);
assertEquals(testName
+ " no-play duck value should be 1.f, but is " + lastVolumeDuck,
1.f, lastVolumeDuck, VOLUME_TOLERANCE);
Log.d(TAG, testName + " volume should be silent and start increasing now");
// we actually start now!
volumeShaperRamp.apply(VolumeShaper.Operation.PLAY);
volumeShaperDuck.apply(VolumeShaper.Operation.PLAY);
Thread.sleep(durationMs / 2);
Log.d(TAG, testName + " volume should be > 0 and about maximum here");
final float lastVolumeRamp2 = volumeShaperRamp.getVolume();
final float lastVolumeDuck2 = volumeShaperDuck.getVolume();
assertTrue(testName
+ " last ramp value should be > 0.f " + lastVolumeRamp2,
lastVolumeRamp2 > 0.f);
assertTrue(testName
+ " last duck value should be < 1.f " + lastVolumeDuck2,
lastVolumeDuck2 < 1.f);
Log.d(TAG, testName + " volume should start decreasing shortly");
Thread.sleep(durationMs / 2 + 1000);
Log.d(TAG, testName + " volume should be silent now");
final float lastVolumeRamp3 = volumeShaperRamp.getVolume();
final float lastVolumeDuck3 = volumeShaperDuck.getVolume();
assertEquals(testName
+ " last ramp value should be 1.f, but is " + lastVolumeRamp3,
1.f, lastVolumeRamp3, VOLUME_TOLERANCE);
assertEquals(testName
+ " last duck value should be 0.f, but is " + lastVolumeDuck3,
0.f, lastVolumeDuck3, VOLUME_TOLERANCE);
runCloseTest(testName, volumeShaperRamp);
runCloseTest(testName, volumeShaperDuck);
}
}
} // testPlayerTwoShapers
// tests that shaper advances in the presence of pause and stop (time based after start).
@LargeTest
public void testPlayerRunDuringPauseStop() throws Exception {
final String TEST_NAME = "testPlayerRunDuringPauseStop";
if (!hasAudioOutput()) {
Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+ "audio output HAL");
return;
}
final VolumeShaper.Configuration config = LINEAR_RAMP;
for (int p = 0; p < PLAYER_TYPES; ++p) {
for (int pause = 0; pause < 2; ++pause) {
if ((p == PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED
|| p == PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED) && pause == 0) {
// Do not test stop and MediaPlayer because a
// MediaPlayer stop requires prepare before starting.
continue;
}
try ( Player player = createPlayer(p);
VolumeShaper volumeShaper = player.createVolumeShaper(config);
) {
final String testName = TEST_NAME + " " + player.name();
Log.d(TAG, testName + " starting volume, should ramp up");
volumeShaper.apply(VolumeShaper.Operation.PLAY);
assertEquals(testName + " volume should be 0.f",
0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
player.start();
Thread.sleep(WARMUP_TIME_MS * 2);
Log.d(TAG, testName + " applying " + (pause != 0 ? "pause" : "stop"));
if (pause == 1) {
player.pause();
} else {
player.stop();
}
Thread.sleep(RAMP_TIME_MS);
Log.d(TAG, testName + " starting again");
player.start();
Thread.sleep(WARMUP_TIME_MS * 2);
Log.d(TAG, testName + " should be full volume");
assertEquals(testName + " volume should be 1.f",
1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
}
}
}
} // testPlayerRunDuringPauseStop
// Player should be started before calling (as it is not an argument to method).
private void runRampTest(String testName, VolumeShaper volumeShaper) throws Exception {
for (VolumeShaper.Configuration config : ALL_STANDARD_RAMPS) {
// This replaces with play.
Log.d(TAG, testName + " Replace + Play (volume should increase)");
volumeShaper.replace(config, VolumeShaper.Operation.PLAY, false /* join */);
Thread.sleep(RAMP_TIME_MS / 2);
// Reverse the direction of the volume shaper curve
Log.d(TAG, testName + " Reverse (volume should decrease)");
volumeShaper.apply(VolumeShaper.Operation.REVERSE);
Thread.sleep(RAMP_TIME_MS / 2 + 1000);
Log.d(TAG, testName + " Check Volume (silent)");
assertEquals(testName + " volume should be 0.f",
0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
// Forwards
Log.d(TAG, testName + " Play (volume should increase)");
volumeShaper.apply(VolumeShaper.Operation.PLAY);
Thread.sleep(RAMP_TIME_MS + 1000);
Log.d(TAG, testName + " Check Volume (volume at max)");
assertEquals(testName + " volume should be 1.f",
1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
// Reverse
Log.d(TAG, testName + " Reverse (volume should decrease)");
volumeShaper.apply(VolumeShaper.Operation.REVERSE);
Thread.sleep(RAMP_TIME_MS + 1000);
Log.d(TAG, testName + " Check Volume (volume should be silent)");
assertEquals(testName + " volume should be 0.f",
0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
// Forwards
Log.d(TAG, testName + " Play (volume should increase)");
volumeShaper.apply(VolumeShaper.Operation.PLAY);
Thread.sleep(RAMP_TIME_MS + 1000);
// Comment out for headset plug/unplug test
// Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
//
Log.d(TAG, testName + " Check Volume (volume at max)");
assertEquals(testName + " volume should be 1.f",
1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
Log.d(TAG, testName + " done");
}
} // runRampTest
// Player should be started before calling (as it is not an argument to method).
private void runDuckTest(String testName, VolumeShaper volumeShaper) throws Exception {
final VolumeShaper.Configuration[] configs = new VolumeShaper.Configuration[] {
LINEAR_DUCK,
};
for (VolumeShaper.Configuration config : configs) {
Log.d(TAG, testName + " Replace + Reverse (volume at max)");
// CORNER CASE: When you replace with REVERSE, it stays at the initial point.
volumeShaper.replace(config, VolumeShaper.Operation.REVERSE, false /* join */);
Thread.sleep(RAMP_TIME_MS / 2);
assertEquals(testName + " volume should be 1.f",
1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
// CORNER CASE: reverse twice doesn't do anything.
Thread.sleep(RAMP_TIME_MS / 2);
Log.d(TAG, testName + " Reverse after reverse (volume at max)");
volumeShaper.apply(VolumeShaper.Operation.REVERSE);
assertEquals(testName + " volume should be 1.f",
1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
Log.d(TAG, testName + " Duck from start (volume should decrease)");
volumeShaper.apply(VolumeShaper.Operation.PLAY);
Thread.sleep(RAMP_TIME_MS * 2);
Log.d(TAG, testName + " Duck done (volume should be low, 0.2f)");
assertEquals(testName + " volume should be 0.2f",
0.2f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
Log.d(TAG, testName + " Unduck (volume should increase)");
volumeShaper.apply(VolumeShaper.Operation.REVERSE);
Thread.sleep(RAMP_TIME_MS * 2);
// Comment out for headset plug/unplug test
// Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
//
Log.d(TAG, testName + " Unduck done (volume at max)");
assertEquals(testName + " volume should be 1.f",
1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
}
} // runDuckTest
// VolumeShaper should not be started prior to this test; it is replaced
// in this test.
private void runRampCornerCaseTest(
String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config)
throws Exception {
// ramps start at 0.f
assertEquals(testName + " volume should be 0.f",
0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
Log.d(TAG, testName + " Reverse at start (quiet now)");
// CORNER CASE: When you begin with REVERSE, it stays at the initial point.
volumeShaper.apply(VolumeShaper.Operation.REVERSE);
Thread.sleep(RAMP_TIME_MS / 2);
assertEquals(testName + " volume should be 0.f",
0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
// CORNER CASE: reverse twice doesn't do anything.
Thread.sleep(RAMP_TIME_MS / 2);
Log.d(TAG, testName + " Reverse after reverse (still quiet)");
volumeShaper.apply(VolumeShaper.Operation.REVERSE);
assertEquals(testName + " volume should be 0.f",
0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
Log.d(TAG, testName + " Ramp from start (volume should increase)");
volumeShaper.apply(VolumeShaper.Operation.PLAY);
Thread.sleep(RAMP_TIME_MS * 2);
Log.d(TAG, testName + " Volume persists at maximum 1.f");
assertEquals(testName + " volume should be 1.f",
1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
Log.d(TAG, testName + " Reverse ramp (volume should decrease)");
volumeShaper.apply(VolumeShaper.Operation.REVERSE);
Thread.sleep(RAMP_TIME_MS / 2);
// join in REVERSE should freeze
final float volume = volumeShaper.getVolume();
Log.d(TAG, testName + " Replace ramp with join in REVERSE (volume steady)");
volumeShaper.replace(config, VolumeShaper.Operation.REVERSE, true /* join */);
Thread.sleep(RAMP_TIME_MS / 2);
// Are we frozen?
final float volume2 = volumeShaper.getVolume();
assertEquals(testName + " volume should be the same (volume steady)",
volume, volume2, JOIN_VOLUME_TOLERANCE);
// Begin playing
Log.d(TAG, testName + " Play joined ramp (volume should increase)");
volumeShaper.apply(VolumeShaper.Operation.PLAY);
Thread.sleep(RAMP_TIME_MS * 2);
// Reverse to get back to start of the joined curve.
Log.d(TAG, testName + " Reverse joined ramp (volume should decrease)");
volumeShaper.apply(VolumeShaper.Operation.REVERSE);
Thread.sleep(RAMP_TIME_MS * 2);
// At this time, we are back to the join point.
// We check now that the scaling for the join is permanent.
Log.d(TAG, testName + " Joined ramp at start (volume same as at join)");
final float volume3 = volumeShaper.getVolume();
assertEquals(testName + " volume should be same as start for joined ramp",
volume2, volume3, JOIN_VOLUME_TOLERANCE);
} // runRampCornerCaseTest
// volumeShaper is closed in this test.
private void runCloseTest(String testName, VolumeShaper volumeShaper) throws Exception {
Log.d(TAG, testName + " closing");
volumeShaper.close();
runCloseTest2(testName, volumeShaper);
} // runCloseTest
// VolumeShaper should be closed prior to this test.
private void runCloseTest2(String testName, VolumeShaper volumeShaper) throws Exception {
// CORNER CASE:
// VolumeShaper methods should throw ISE after closing.
Log.d(TAG, testName + " getVolume() after close should throw ISE");
assertThrows(IllegalStateException.class,
volumeShaper::getVolume);
Log.d(TAG, testName + " apply() after close should throw ISE");
assertThrows(IllegalStateException.class,
()->{ volumeShaper.apply(VolumeShaper.Operation.REVERSE); });
Log.d(TAG, testName + " replace() after close should throw ISE");
assertThrows(IllegalStateException.class,
()->{ volumeShaper.replace(
LINEAR_RAMP, VolumeShaper.Operation.PLAY, false /* join */); });
Log.d(TAG, testName + " closing x2 is OK");
volumeShaper.close(); // OK to close twice.
Log.d(TAG, testName + " closing x3 is OK");
volumeShaper.close(); // OK to close thrice.
} // runCloseTest2
// Player should not be started prior to calling (it is started in this test)
// VolumeShaper should not be started prior to calling (it is not started in this test).
private void runStartIdleTest(String testName, VolumeShaper volumeShaper, Player player)
throws Exception {
Log.d(TAG, testName + " volume after creation or pause doesn't advance (silent now)");
// CORNER CASE:
// volumeShaper volume after creation or pause doesn't advance.
Thread.sleep(WARMUP_TIME_MS);
assertEquals(testName + " volume should be 0.f",
0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
player.start();
Thread.sleep(WARMUP_TIME_MS);
Log.d(TAG, testName + " volume after player start doesn't advance if play isn't called."
+ " (still silent)");
// CORNER CASE:
// volumeShaper volume after creation doesn't or pause doesn't advance even
// after the player starts.
Thread.sleep(WARMUP_TIME_MS);
assertEquals(testName + " volume should be 0.f",
0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
} // runStartIdleTest
// Player should not be running prior to calling (it is started in this test).
// VolumeShaper is also started in this test.
private void runStartSyncTest(String testName, VolumeShaper volumeShaper, Player player)
throws Exception {
Log.d(TAG, testName + " volume after creation or pause doesn't advance "
+ "if player isn't started. (silent now)");
volumeShaper.apply(VolumeShaper.Operation.PLAY);
// CORNER CASE:
// volumeShaper volume after creation or pause doesn't advance
// even after play is called.
Thread.sleep(WARMUP_TIME_MS);
assertEquals(testName + " volume should be 0.f",
0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
Log.d(TAG, testName + " starting now (volume should increase)");
player.start();
Thread.sleep(WARMUP_TIME_MS);
Log.d(TAG, testName + " volume after player start advances if play is called.");
// CORNER CASE:
// Now volume should have advanced since play is called.
Thread.sleep(WARMUP_TIME_MS);
assertTrue(testName + " volume should be greater than 0.f",
volumeShaper.getVolume() > 0.f);
} // runStartSyncTest
}