| /* |
| * 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.audio.cts; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.testng.Assert.assertThrows; |
| |
| 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.media.audio.cts.R; |
| import android.media.cts.AudioHelper; |
| import android.os.Parcel; |
| import android.os.PowerManager; |
| import android.platform.test.annotations.AppModeFull; |
| import android.util.Log; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.filters.FlakyTest; |
| import androidx.test.filters.LargeTest; |
| import androidx.test.filters.SmallTest; |
| |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.Arrays; |
| |
| import junitparams.JUnitParamsRunner; |
| import junitparams.Parameters; |
| |
| /** |
| * 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") |
| @RunWith(JUnitParamsRunner.class) |
| public class VolumeShaperTest { |
| 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(); |
| |
| // This linear ramp VolumeShaper runs on media time, not clock time. |
| // Note: for direct or offloaded audio, media time is not supported, so clock time is used. |
| private static final VolumeShaper.Configuration LINEAR_RAMP_MEDIA_TIME = |
| new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP) |
| .setDuration(RAMP_TIME_MS) |
| .setOptionFlags(0) // clears flags, operates on media time. |
| .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 | |
| VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME) |
| .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.Configuration[] MONOTONIC_RAMPS = { |
| MONOTONIC_TEST, |
| CUBIC_RAMP, |
| SCURVE_RAMP, |
| SINE_RAMP, |
| }; |
| |
| 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(boolean allowOffload) { |
| 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 int usage = allowOffload |
| ? AudioAttributes.USAGE_MEDIA : AudioAttributes.USAGE_ALARM; |
| |
| final AudioTrack audioTrack = new AudioTrack.Builder() |
| .setAudioAttributes(new AudioAttributes.Builder() |
| .setUsage(usage) |
| .build()) |
| .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 { |
| AudioTrackPlayer(boolean allowOffload) { |
| mTrack = createSineAudioTrack(allowOffload); |
| 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 static final int PLAYER_TYPE_AUDIO_TRACK_NON_OFFLOADED = 3; |
| |
| private Player createPlayer(int type) { |
| switch (type) { |
| case PLAYER_TYPE_AUDIO_TRACK: |
| return new AudioTrackPlayer(true /* allowOffload */); |
| case PLAYER_TYPE_AUDIO_TRACK_NON_OFFLOADED: |
| return new AudioTrackPlayer(false /* allowOffload */); |
| 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 |
| @Test |
| 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 |
| @Test |
| 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 |
| @Test |
| 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 |
| @Test |
| 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 |
| |
| @Test |
| public void testDuckAudioTrack() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) { |
| runTestDuckPlayer("testDuckAudioTrack", player); |
| } |
| } |
| |
| @Test |
| public void testDuckMediaPlayerNonOffloaded() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) { |
| runTestDuckPlayer("testDuckMediaPlayerNonOffloaded", player); |
| } |
| } |
| |
| @Test |
| public void testDuckMediaPlayerOffloaded() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) { |
| runTestDuckPlayer("testDuckMediaPlayerOffloaded", player); |
| } |
| } |
| |
| private void runTestDuckPlayer(String testName, Player player) throws Exception { |
| if (!hasAudioOutput()) { |
| Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " |
| + "audio output HAL"); |
| return; |
| } |
| |
| try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) { |
| Log.d(TAG, testName + " starting"); |
| player.start(); |
| Thread.sleep(WARMUP_TIME_MS); |
| |
| runDuckTest(testName, volumeShaper); |
| runCloseTest(testName, volumeShaper); |
| } |
| } |
| |
| private VolumeShaper.Configuration[] getAllStandardRamps() { |
| return ALL_STANDARD_RAMPS; |
| } |
| |
| // Parameters are indices to ALL_STANDARD_RAMPS configuration array. |
| @Test |
| @Parameters(method = "getAllStandardRamps") |
| public void testRampAudioTrack( |
| VolumeShaper.Configuration configuration) throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) { |
| runTestRampPlayer("testRampAudioTrack", player, configuration); |
| } |
| } |
| |
| // Parameters are indices to ALL_STANDARD_RAMPS configuration array. |
| @Test |
| @Parameters(method = "getAllStandardRamps") |
| public void testRampMediaPlayerNonOffloaded( |
| VolumeShaper.Configuration configuration) throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) { |
| runTestRampPlayer("testRampMediaPlayerNonOffloaded", player, configuration); |
| } |
| } |
| |
| // Parameters are indices to ALL_STANDARD_RAMPS configuration array. |
| @Test |
| @Parameters(method = "getAllStandardRamps") |
| public void testRampMediaPlayerOffloaded( |
| VolumeShaper.Configuration configuration) throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) { |
| runTestRampPlayer("testRampMediaPlayerOffloaded", player, configuration); |
| } |
| } |
| |
| private void runTestRampPlayer( |
| String testName, Player player, VolumeShaper.Configuration configuration) |
| throws Exception { |
| if (!hasAudioOutput()) { |
| Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " |
| + "audio output HAL"); |
| return; |
| } |
| |
| try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) { |
| Log.d(TAG, testName + " starting"); |
| player.start(); |
| Thread.sleep(WARMUP_TIME_MS); |
| |
| runRampTest(testName, volumeShaper, configuration); |
| runCloseTest(testName, volumeShaper); |
| } |
| } |
| |
| @FlakyTest |
| @Test |
| public void testCornerCaseAudioTrack() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) { |
| runTestCornerCasePlayer("testCornerCaseAudioTrack", player); |
| } |
| } |
| |
| @FlakyTest |
| @Test |
| public void testCornerCaseMediaPlayerNonOffloaded() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) { |
| runTestCornerCasePlayer("testCornerCaseMediaPlayerNonOffloaded", player); |
| } |
| } |
| |
| @FlakyTest |
| @Test |
| public void testCornerCaseMediaPlayerOffloaded() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) { |
| runTestCornerCasePlayer("testCornerCaseMediaPlayerOffloaded", player); |
| } |
| } |
| |
| private void runTestCornerCasePlayer(String testName, Player player) throws Exception { |
| 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; |
| VolumeShaper volumeShaper = null; |
| try { |
| volumeShaper = player.createVolumeShaper(config); |
| |
| 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(); |
| } |
| } |
| } // runTestCornerCasePlayer |
| |
| @FlakyTest |
| @LargeTest |
| @Test |
| 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 |
| @Test |
| 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 = 5000; |
| 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 |
| |
| private VolumeShaper.Configuration[] getMonotonicRamps() { |
| return MONOTONIC_RAMPS; |
| } |
| |
| // Parameters are indices to MONOTONIC_RAMPS configuration array. |
| @Test |
| @Parameters(method = "getMonotonicRamps") |
| public void testCubicMonotonicAudioTrack( |
| VolumeShaper.Configuration configuration) throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) { |
| runTestCubicMonotonicPlayer( |
| "testCubicMonotonicAudioTrack", player, configuration); |
| } |
| } |
| |
| // Parameters are indices to MONOTONIC_RAMPS configuration array. |
| @Test |
| @Parameters(method = "getMonotonicRamps") |
| public void testCubicMonotonicMediaPlayerNonOffloaded( |
| VolumeShaper.Configuration configuration) throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) { |
| runTestCubicMonotonicPlayer( |
| "testCubicMonotonicMediaPlayerNonOffloaded", player, configuration); |
| } |
| } |
| |
| // Parameters are indices to MONOTONIC_RAMPS configuration array. |
| @Test |
| @Parameters(method = "getMonotonicRamps") |
| public void testCubicMonotonicMediaPlayerOffloaded( |
| VolumeShaper.Configuration configuration) throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) { |
| runTestCubicMonotonicPlayer( |
| "testCubicMonotonicMediaPlayerOffloaded", player, configuration); |
| } |
| } |
| |
| private void runTestCubicMonotonicPlayer( |
| String testName, Player player, VolumeShaper.Configuration configuration) |
| throws Exception { |
| if (!hasAudioOutput()) { |
| Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " |
| + "audio output HAL"); |
| return; |
| } |
| |
| try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) { |
| volumeShaper.apply(VolumeShaper.Operation.PLAY); |
| player.start(); |
| Thread.sleep(WARMUP_TIME_MS); |
| |
| // 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); |
| } |
| } // runTestCubicMonotonicPlayer |
| |
| @Test |
| public void testStepRampAudioTrack() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) { |
| runTestStepRampPlayer("testStepRampAudioTrack", player); |
| } |
| } |
| |
| @Test |
| public void testStepRampMediaPlayerNonOffloaded() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) { |
| runTestStepRampPlayer("testStepRampMediaPlayerNonOffloaded", player); |
| } |
| } |
| |
| @Test |
| public void testStepRampMediaPlayerOffloaded() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) { |
| runTestStepRampPlayer("testStepRampMediaPlayerOffloaded", player); |
| } |
| } |
| |
| private void runTestStepRampPlayer(String testName, Player player) throws Exception { |
| 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. |
| |
| try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) { |
| 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); |
| } |
| } // runTestStepRampPlayer |
| |
| @Test |
| public void testTwoShapersAudioTrack() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) { |
| runTestTwoShapersPlayer("testTwoShapersAudioTrack", player); |
| } |
| } |
| |
| @Test |
| public void testTwoShapersMediaPlayerNonOffloaded() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) { |
| runTestTwoShapersPlayer("testTwoShapersMediaPlayerNonOffloaded", player); |
| } |
| } |
| |
| @Test |
| public void testTwoShapersMediaPlayerOffloaded() throws Exception { |
| try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) { |
| runTestTwoShapersPlayer("testTwoShapersMediaPlayerOffloaded", player); |
| } |
| } |
| |
| private void runTestTwoShapersPlayer(String testName, Player player) throws Exception { |
| |
| 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(); |
| |
| try ( |
| VolumeShaper volumeShaperRamp = player.createVolumeShaper(LONG_RAMP); |
| VolumeShaper volumeShaperDuck = player.createVolumeShaper(LONG_DUCK); |
| ) { |
| 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); |
| } |
| } // runTestTwoShapersPlayer |
| |
| // tests that shaper which is based on clocktime after start (default on builder) |
| // advances in the presence of pause and stop. |
| @LargeTest |
| @Test |
| public void testPlayerRunDuringPauseStop() throws Exception { |
| runTestPlayerDuringPauseStop("testPlayerRunDuringPauseStop", false /* useMediaTime */); |
| } |
| |
| // tests that shaper which is based on media time will freeze |
| // in the presence of pause and stop. |
| @LargeTest |
| @Test |
| public void testPlayerFreezeDuringPauseStop() throws Exception { |
| runTestPlayerDuringPauseStop("testPlayerFreezeDuringPauseStop", true /* useMediaTime */); |
| } |
| |
| private void runTestPlayerDuringPauseStop( |
| String parentTestName, boolean useMediaTime) throws Exception { |
| 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 = |
| useMediaTime ? LINEAR_RAMP_MEDIA_TIME : 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; |
| } |
| if (useMediaTime && p == PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED) { |
| continue; // Offloaded media time not supported. |
| } |
| // For this test, force non offload track for media time, |
| // as media time based offload/direct volumeshaper is not supported yet. |
| // TODO(b/236187574) - remove this requirement. |
| if (useMediaTime && p == PLAYER_TYPE_AUDIO_TRACK) { |
| p = PLAYER_TYPE_AUDIO_TRACK_NON_OFFLOADED; |
| } |
| |
| try ( Player player = createPlayer(p); |
| VolumeShaper volumeShaper = player.createVolumeShaper(config); |
| ) { |
| final String testName = parentTestName + " " + 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); |
| |
| String operation = pause != 0 ? "pause" : "stop"; |
| Log.d(TAG, testName + " applying " + operation); |
| if (pause == 1) { |
| player.pause(); |
| } else { |
| player.stop(); |
| } |
| Log.d(TAG, testName + " volume right after " + |
| operation + " is " + volumeShaper.getVolume()); |
| |
| Thread.sleep(RAMP_TIME_MS); |
| |
| Log.d(TAG, testName + " volume after " + operation + |
| " and sleep is " + volumeShaper.getVolume()); |
| |
| Log.d(TAG, testName + " starting again"); |
| player.start(); |
| Thread.sleep(WARMUP_TIME_MS * 2); |
| |
| final float finalVolume = volumeShaper.getVolume(); |
| if (useMediaTime) { |
| Log.d(TAG, testName + " final volume after starting should be " + |
| "less than full volume, actual is " + finalVolume); |
| assertThat(finalVolume).isLessThan(1.f); |
| } else { |
| Log.d(TAG, testName + " final volume after starting should be " + |
| "full volume, actual is " + finalVolume); |
| assertEquals(testName + " volume should be 1.f", |
| 1.f, finalVolume, VOLUME_TOLERANCE); |
| } |
| } |
| } |
| } |
| } // runTestPlayerDuringPauseStop |
| |
| // Player should be started before calling (as it is not an argument to method). |
| private void runRampTest( |
| String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config) |
| throws Exception { |
| // 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 |
| |
| private static Context getContext() { |
| return InstrumentationRegistry.getInstrumentation().getTargetContext(); |
| } |
| } |