| /* |
| * 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.AudioSystem; |
| import android.media.AudioTrack; |
| import android.media.MediaPlayer; |
| import android.media.PlaybackParams; |
| import android.media.VolumeShaper; |
| import android.os.Parcel; |
| import android.os.PowerManager; |
| 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 |
| */ |
| 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(); |
| |
| 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(), |
| AudioSystem.newAudioSessionId()); |
| 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 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 |
| } |