1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.cts; 18 19 import static org.testng.Assert.assertThrows; 20 21 import android.media.cts.R; 22 23 import android.app.ActivityManager; 24 import android.content.Context; 25 import android.content.pm.PackageManager; 26 import android.media.AudioAttributes; 27 import android.media.AudioFormat; 28 import android.media.AudioManager; 29 import android.media.AudioTrack; 30 import android.media.MediaPlayer; 31 import android.media.VolumeShaper; 32 import android.os.Parcel; 33 import android.os.PowerManager; 34 import android.platform.test.annotations.AppModeFull; 35 import android.support.test.filters.FlakyTest; 36 import android.support.test.filters.LargeTest; 37 import android.support.test.filters.MediumTest; 38 import android.support.test.filters.SmallTest; 39 import android.util.Log; 40 41 import com.android.compatibility.common.util.CtsAndroidTestCase; 42 43 import java.lang.AutoCloseable; 44 import java.util.Arrays; 45 46 /** 47 * VolumeShaperTest is automated using VolumeShaper.getVolume() to verify that a ramp 48 * or a duck is at the expected volume level. Listening to some tests is also possible, 49 * as we logcat the expected volume change. 50 * 51 * To see the listening messages: 52 * 53 * adb logcat | grep VolumeShaperTest 54 */ 55 @AppModeFull(reason = "TODO: evaluate and port to instant") 56 public class VolumeShaperTest extends CtsAndroidTestCase { 57 private static final String TAG = "VolumeShaperTest"; 58 59 // ramp or duck time (duration) used in tests. 60 private static final long RAMP_TIME_MS = 3000; 61 62 // volume tolerance for completion volume checks. 63 private static final float VOLUME_TOLERANCE = 0.0000001f; 64 65 // volume difference permitted on replace() with join. 66 private static final float JOIN_VOLUME_TOLERANCE = 0.1f; 67 68 // time to wait for player state change 69 private static final long WARMUP_TIME_MS = 300; 70 71 private static final VolumeShaper.Configuration SILENCE = 72 new VolumeShaper.Configuration.Builder() 73 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) 74 .setCurve(new float[] { 0.f, 1.f } /* times */, 75 new float[] { 0.f, 0.f } /* volumes */) 76 .setDuration(RAMP_TIME_MS) 77 .build(); 78 79 // Duck configurations go from 1.f down to 0.2f (not full ramp down). 80 private static final VolumeShaper.Configuration LINEAR_DUCK = 81 new VolumeShaper.Configuration.Builder() 82 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) 83 .setCurve(new float[] { 0.f, 1.f } /* times */, 84 new float[] { 1.f, 0.2f } /* volumes */) 85 .setDuration(RAMP_TIME_MS) 86 .build(); 87 88 // Ramp configurations go from 0.f up to 1.f 89 private static final VolumeShaper.Configuration LINEAR_RAMP = 90 new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP) 91 .setDuration(RAMP_TIME_MS) 92 .build(); 93 94 private static final VolumeShaper.Configuration CUBIC_RAMP = 95 new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.CUBIC_RAMP) 96 .setDuration(RAMP_TIME_MS) 97 .build(); 98 99 private static final VolumeShaper.Configuration SINE_RAMP = 100 new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.SINE_RAMP) 101 .setDuration(RAMP_TIME_MS) 102 .build(); 103 104 private static final VolumeShaper.Configuration SCURVE_RAMP = 105 new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.SCURVE_RAMP) 106 .setDuration(RAMP_TIME_MS) 107 .build(); 108 109 // internal use only 110 private static final VolumeShaper.Configuration LOG_RAMP = 111 new VolumeShaper.Configuration.Builder() 112 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR) 113 .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_VOLUME_IN_DBFS) 114 .setCurve(new float[] { 0.f, 1.f } /* times */, 115 new float[] { -80.f, 0.f } /* volumes */) 116 .setDuration(RAMP_TIME_MS) 117 .build(); 118 119 // a step ramp is not continuous, so we have a different test for it. 120 private static final VolumeShaper.Configuration STEP_RAMP = 121 new VolumeShaper.Configuration.Builder() 122 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_STEP) 123 .setCurve(new float[] { 0.f, 1.f } /* times */, 124 new float[] { 0.f, 1.f } /* volumes */) 125 .setDuration(RAMP_TIME_MS) 126 .build(); 127 128 private static final VolumeShaper.Configuration[] ALL_STANDARD_RAMPS = { 129 LINEAR_RAMP, 130 CUBIC_RAMP, 131 SINE_RAMP, 132 SCURVE_RAMP, 133 }; 134 135 private static final VolumeShaper.Configuration[] TEST_DUCKS = { 136 LINEAR_DUCK, 137 }; 138 139 // this ramp should result in non-monotonic behavior with a typical cubic spline. 140 private static final VolumeShaper.Configuration MONOTONIC_TEST = 141 new VolumeShaper.Configuration.Builder() 142 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC_MONOTONIC) 143 .setCurve(new float[] { 0.f, 0.3f, 0.7f, 1.f } /* times */, 144 new float[] { 0.f, 0.5f, 0.5f, 1.f } /* volumes */) 145 .setDuration(RAMP_TIME_MS) 146 .build(); 147 148 private static final VolumeShaper.Configuration MONOTONIC_TEST_FAIL = 149 new VolumeShaper.Configuration.Builder(MONOTONIC_TEST) 150 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC) 151 .build(); 152 153 private static final VolumeShaper.Operation[] ALL_STANDARD_OPERATIONS = { 154 VolumeShaper.Operation.PLAY, 155 VolumeShaper.Operation.REVERSE, 156 }; 157 158 private boolean hasAudioOutput() { 159 return getContext().getPackageManager() 160 .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT); 161 } 162 163 private boolean isLowRamDevice() { 164 return ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE)) 165 .isLowRamDevice(); 166 } 167 168 private static AudioTrack createSineAudioTrack() { 169 final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT; 170 final int TEST_MODE = AudioTrack.MODE_STATIC; 171 final int TEST_SR = 48000; 172 final AudioFormat format = new AudioFormat.Builder() 173 .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) 174 .setEncoding(TEST_FORMAT) 175 .setSampleRate(TEST_SR) 176 .build(); 177 178 final int frameCount = AudioHelper.frameCountFromMsec(100 /*ms*/, format); 179 final int frameSize = AudioHelper.frameSizeFromFormat(format); 180 181 final AudioTrack audioTrack = new AudioTrack.Builder() 182 .setAudioFormat(format) 183 .setBufferSizeInBytes(frameCount * frameSize) 184 .setTransferMode(TEST_MODE) 185 .build(); 186 // create float array and write it 187 final int sampleCount = frameCount * format.getChannelCount(); 188 final float[] vaf = AudioHelper.createSoundDataInFloatArray( 189 sampleCount, TEST_SR, 190 600 * format.getChannelCount() /* frequency */, 0 /* sweep */); 191 assertEquals(vaf.length, audioTrack.write(vaf, 0 /* offsetInFloats */, vaf.length, 192 AudioTrack.WRITE_NON_BLOCKING)); 193 audioTrack.setLoopPoints(0, frameCount, -1 /* loopCount */); 194 return audioTrack; 195 } 196 197 private MediaPlayer createMediaPlayer(boolean offloaded) { 198 // MP3 resource should be greater than 1m to introduce offloading 199 final int RESOURCE_ID = R.raw.test1m1s; 200 201 final MediaPlayer mediaPlayer = MediaPlayer.create(getContext(), 202 RESOURCE_ID, 203 new AudioAttributes.Builder() 204 .setUsage(offloaded ? 205 AudioAttributes.USAGE_MEDIA // offload allowed 206 : AudioAttributes.USAGE_NOTIFICATION) // offload not allowed 207 .build(), 208 new AudioManager(getContext()).generateAudioSessionId()); 209 mediaPlayer.setWakeMode(getContext(), PowerManager.PARTIAL_WAKE_LOCK); 210 mediaPlayer.setLooping(true); 211 return mediaPlayer; 212 } 213 214 private static void checkEqual(String testName, 215 VolumeShaper.Configuration expected, VolumeShaper.Configuration actual) { 216 assertEquals(testName + " configuration should be equal", 217 expected, actual); 218 assertEquals(testName + " configuration.hashCode() should be equal", 219 expected.hashCode(), actual.hashCode()); 220 assertEquals(testName + " configuration.toString() should be equal", 221 expected.toString(), actual.toString()); 222 } 223 224 private static void checkNotEqual(String testName, 225 VolumeShaper.Configuration notEqual, VolumeShaper.Configuration actual) { 226 assertTrue(testName + " configuration should not be equal", 227 !actual.equals(notEqual)); 228 assertTrue(testName + " configuration.hashCode() should not be equal", 229 actual.hashCode() != notEqual.hashCode()); 230 assertTrue(testName + " configuration.toString() should not be equal", 231 !actual.toString().equals(notEqual.toString())); 232 } 233 234 // generic player class to simplify testing 235 private interface Player extends AutoCloseable { 236 public void start(); 237 public void pause(); 238 public void stop(); 239 @Override public void close(); 240 public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration); 241 public String name(); 242 } 243 244 private static class AudioTrackPlayer implements Player { 245 public AudioTrackPlayer() { 246 mTrack = createSineAudioTrack(); 247 mName = new String("AudioTrack"); 248 } 249 250 @Override public void start() { 251 mTrack.play(); 252 } 253 254 @Override public void pause() { 255 mTrack.pause(); 256 } 257 258 @Override public void stop() { 259 mTrack.stop(); 260 } 261 262 @Override public void close() { 263 mTrack.release(); 264 } 265 266 @Override 267 public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration) { 268 return mTrack.createVolumeShaper(configuration); 269 } 270 271 @Override public String name() { 272 return mName; 273 } 274 275 private final AudioTrack mTrack; 276 private final String mName; 277 } 278 279 private class MediaPlayerPlayer implements Player { 280 public MediaPlayerPlayer(boolean offloaded) { 281 mPlayer = createMediaPlayer(offloaded); 282 mName = new String("MediaPlayer" + (offloaded ? "Offloaded" : "NonOffloaded")); 283 } 284 285 @Override public void start() { 286 mPlayer.start(); 287 } 288 289 @Override public void pause() { 290 mPlayer.pause(); 291 } 292 293 @Override public void stop() { 294 mPlayer.stop(); 295 } 296 297 @Override public void close() { 298 mPlayer.release(); 299 } 300 301 @Override 302 public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration) { 303 return mPlayer.createVolumeShaper(configuration); 304 } 305 306 @Override public String name() { 307 return mName; 308 } 309 310 private final MediaPlayer mPlayer; 311 private final String mName; 312 } 313 314 private static final int PLAYER_TYPES = 3; 315 private static final int PLAYER_TYPE_AUDIO_TRACK = 0; 316 private static final int PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED = 1; 317 private static final int PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED = 2; 318 319 private Player createPlayer(int type) { 320 switch (type) { 321 case PLAYER_TYPE_AUDIO_TRACK: 322 return new AudioTrackPlayer(); 323 case PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED: 324 return new MediaPlayerPlayer(false /* offloaded */); 325 case PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED: 326 return new MediaPlayerPlayer(true /* offloaded */); 327 default: 328 return null; 329 } 330 } 331 332 private static void testBuildRamp(int points) { 333 float[] ramp = new float[points]; 334 final float fscale = 1.f / (points - 1); 335 for (int i = 0; i < points; ++i) { 336 ramp[i] = i * fscale; 337 } 338 ramp[points - 1] = 1.f; 339 // does it build? 340 final VolumeShaper.Configuration config = new VolumeShaper.Configuration.Builder() 341 .setCurve(ramp, ramp) 342 .build(); 343 } 344 345 @SmallTest 346 public void testVolumeShaperConfigurationBuilder() throws Exception { 347 final String TEST_NAME = "testVolumeShaperConfigurationBuilder"; 348 349 // Verify that IllegalStateExceptions are properly triggered 350 // for methods with no arguments. 351 352 Log.d(TAG, TEST_NAME + " configuration builder should throw ISE if no curve specified"); 353 assertThrows(IllegalStateException.class, 354 new VolumeShaper.Configuration.Builder() 355 ::build); 356 357 assertThrows(IllegalStateException.class, 358 new VolumeShaper.Configuration.Builder() 359 ::invertVolumes); 360 361 assertThrows(IllegalStateException.class, 362 new VolumeShaper.Configuration.Builder() 363 ::reflectTimes); 364 365 Log.d(TAG, TEST_NAME + " configuration builder should IAE on invalid curve"); 366 // Verify IllegalArgumentExceptions are properly triggered 367 // for methods with arguments. 368 final float[] ohOne = { 0.f, 1.f }; 369 final float[][] invalidCurves = { 370 { -1.f, 1.f }, 371 { 0.5f }, 372 { 0.f, 2.f }, 373 }; 374 for (float[] invalidCurve : invalidCurves) { 375 assertThrows(IllegalArgumentException.class, 376 () -> { 377 new VolumeShaper.Configuration.Builder() 378 .setCurve(invalidCurve, ohOne) 379 .build(); }); 380 381 assertThrows(IllegalArgumentException.class, 382 () -> { 383 new VolumeShaper.Configuration.Builder() 384 .setCurve(ohOne, invalidCurve) 385 .build(); }); 386 } 387 388 Log.d(TAG, TEST_NAME + " configuration builder should throw IAE on invalid duration"); 389 assertThrows(IllegalArgumentException.class, 390 () -> { 391 new VolumeShaper.Configuration.Builder() 392 .setCurve(ohOne, ohOne) 393 .setDuration(-1) 394 .build(); }); 395 396 Log.d(TAG, TEST_NAME + " configuration builder should throw IAE on invalid interpolator"); 397 assertThrows(IllegalArgumentException.class, 398 () -> { 399 new VolumeShaper.Configuration.Builder() 400 .setCurve(ohOne, ohOne) 401 .setInterpolatorType(-1) 402 .build(); }); 403 404 // Verify defaults. 405 // Use the Builder with setCurve(ohOne, ohOne). 406 final VolumeShaper.Configuration config = 407 new VolumeShaper.Configuration.Builder().setCurve(ohOne, ohOne).build(); 408 assertEquals(TEST_NAME + " default interpolation should be cubic", 409 VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC, config.getInterpolatorType()); 410 assertEquals(TEST_NAME + " default duration should be 1000 ms", 411 1000, config.getDuration()); 412 assertTrue(TEST_NAME + " times should be { 0.f, 1.f }", 413 Arrays.equals(ohOne, config.getTimes())); 414 assertTrue(TEST_NAME + " volumes should be { 0.f, 1.f }", 415 Arrays.equals(ohOne, config.getVolumes())); 416 417 // Due to precision problems, we cannot have ramps that do not have 418 // perfect binary representation for equality comparison. 419 // (For example, 0.1 is a repeating mantissa in binary, 420 // but 0.25, 0.5 can be expressed with few mantissa bits). 421 final float[] binaryCurve1 = { 0.f, 0.25f, 0.5f, 0.625f, 1.f }; 422 final float[] binaryCurve2 = { 0.f, 0.125f, 0.375f, 0.75f, 1.f }; 423 final VolumeShaper.Configuration[] BINARY_RAMPS = { 424 LINEAR_RAMP, 425 CUBIC_RAMP, 426 new VolumeShaper.Configuration.Builder() 427 .setCurve(binaryCurve1, binaryCurve2) 428 .build(), 429 }; 430 431 // Verify volume inversion and time reflection work as expected 432 // with ramps (which start at { 0.f, 0.f } and end at { 1.f, 1.f }). 433 for (VolumeShaper.Configuration testRamp : BINARY_RAMPS) { 434 VolumeShaper.Configuration ramp; 435 ramp = new VolumeShaper.Configuration.Builder(testRamp).build(); 436 checkEqual(TEST_NAME, testRamp, ramp); 437 438 ramp = new VolumeShaper.Configuration.Builder(testRamp) 439 .setDuration(10) 440 .build(); 441 checkNotEqual(TEST_NAME, testRamp, ramp); 442 443 ramp = new VolumeShaper.Configuration.Builder(testRamp).build(); 444 checkEqual(TEST_NAME, testRamp, ramp); 445 446 ramp = new VolumeShaper.Configuration.Builder(testRamp) 447 .invertVolumes() 448 .build(); 449 checkNotEqual(TEST_NAME, testRamp, ramp); 450 451 ramp = new VolumeShaper.Configuration.Builder(testRamp) 452 .invertVolumes() 453 .invertVolumes() 454 .build(); 455 checkEqual(TEST_NAME, testRamp, ramp); 456 457 ramp = new VolumeShaper.Configuration.Builder(testRamp) 458 .reflectTimes() 459 .build(); 460 checkNotEqual(TEST_NAME, testRamp, ramp); 461 462 ramp = new VolumeShaper.Configuration.Builder(testRamp) 463 .reflectTimes() 464 .reflectTimes() 465 .build(); 466 checkEqual(TEST_NAME, testRamp, ramp); 467 468 // check scaling start and end volumes 469 ramp = new VolumeShaper.Configuration.Builder(testRamp) 470 .scaleToStartVolume(0.5f) 471 .build(); 472 checkNotEqual(TEST_NAME, testRamp, ramp); 473 474 ramp = new VolumeShaper.Configuration.Builder(testRamp) 475 .scaleToStartVolume(0.5f) 476 .scaleToStartVolume(0.f) 477 .build(); 478 checkEqual(TEST_NAME, testRamp, ramp); 479 480 ramp = new VolumeShaper.Configuration.Builder(testRamp) 481 .scaleToStartVolume(0.5f) 482 .scaleToEndVolume(0.f) 483 .scaleToStartVolume(1.f) 484 .invertVolumes() 485 .build(); 486 checkEqual(TEST_NAME, testRamp, ramp); 487 } 488 489 // check that getMaximumCurvePoints() returns the correct value 490 final int maxPoints = VolumeShaper.Configuration.getMaximumCurvePoints(); 491 492 testBuildRamp(maxPoints); // no exceptions here. 493 494 if (maxPoints < Integer.MAX_VALUE) { 495 Log.d(TAG, TEST_NAME + " configuration builder " 496 + "should throw IAE if getMaximumCurvePoints() exceeded"); 497 assertThrows(IllegalArgumentException.class, 498 () -> { testBuildRamp(maxPoints + 1); }); 499 } 500 } // testVolumeShaperConfigurationBuilder 501 502 @SmallTest 503 public void testVolumeShaperConfigurationParcelable() throws Exception { 504 final String TEST_NAME = "testVolumeShaperConfigurationParcelable"; 505 506 for (VolumeShaper.Configuration config : ALL_STANDARD_RAMPS) { 507 assertEquals(TEST_NAME + " no parceled file descriptors", 508 0 /* expected */, config.describeContents()); 509 510 final Parcel srcParcel = Parcel.obtain(); 511 config.writeToParcel(srcParcel, 0 /* flags */); 512 513 final byte[] marshallBuffer = srcParcel.marshall(); 514 515 final Parcel dstParcel = Parcel.obtain(); 516 dstParcel.unmarshall(marshallBuffer, 0 /* offset */, marshallBuffer.length); 517 dstParcel.setDataPosition(0); 518 519 final VolumeShaper.Configuration restoredConfig = 520 VolumeShaper.Configuration.CREATOR.createFromParcel(dstParcel); 521 assertEquals(TEST_NAME + 522 " marshalled/restored VolumeShaper.Configuration should match", 523 config, restoredConfig); 524 } 525 } // testVolumeShaperConfigurationParcelable 526 527 @SmallTest 528 public void testVolumeShaperOperationParcelable() throws Exception { 529 final String TEST_NAME = "testVolumeShaperOperationParcelable"; 530 531 for (VolumeShaper.Operation operation : ALL_STANDARD_OPERATIONS) { 532 assertEquals(TEST_NAME + " no parceled file descriptors", 533 0 /* expected */, operation.describeContents()); 534 535 final Parcel srcParcel = Parcel.obtain(); 536 operation.writeToParcel(srcParcel, 0 /* flags */); 537 538 final byte[] marshallBuffer = srcParcel.marshall(); 539 540 final Parcel dstParcel = Parcel.obtain(); 541 dstParcel.unmarshall(marshallBuffer, 0 /* offset */, marshallBuffer.length); 542 dstParcel.setDataPosition(0); 543 544 final VolumeShaper.Operation restoredOperation = 545 VolumeShaper.Operation.CREATOR.createFromParcel(dstParcel); 546 assertEquals(TEST_NAME + 547 " marshalled/restored VolumeShaper.Operation should match", 548 operation, restoredOperation); 549 } 550 } // testVolumeShaperOperationParcelable 551 552 // This tests that we can't create infinite shapers and cause audioserver 553 // to crash due to memory or performance issues. Typically around 16 app based 554 // shapers are allowed by the audio server. 555 @SmallTest 556 public void testMaximumShapers() { 557 final String TEST_NAME = "testMaximumShapers"; 558 if (!hasAudioOutput()) { 559 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 560 + "audio output HAL"); 561 return; 562 } 563 564 final int WAY_TOO_MANY_SHAPERS = 1000; 565 566 for (int p = 0; p < PLAYER_TYPES; ++p) { 567 try (Player player = createPlayer(p)) { 568 final String testName = TEST_NAME + " " + player.name(); 569 final VolumeShaper[] shapers = new VolumeShaper[WAY_TOO_MANY_SHAPERS]; 570 int i = 0; 571 try { 572 for (; i < shapers.length; ++i) { 573 shapers[i] = player.createVolumeShaper(SILENCE); 574 } 575 fail(testName + " should not be able to create " 576 + shapers.length + " shapers"); 577 } catch (IllegalStateException ise) { 578 Log.d(TAG, testName + " " + i + " shapers created before failure (OK)"); 579 } 580 } 581 // volume shapers close when player closes. 582 } 583 } // testMaximumShapers 584 585 @LargeTest 586 public void testPlayerDuck() throws Exception { 587 final String TEST_NAME = "testPlayerDuck"; 588 if (!hasAudioOutput()) { 589 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 590 + "audio output HAL"); 591 return; 592 } 593 594 for (int p = 0; p < PLAYER_TYPES; ++p) { 595 try ( Player player = createPlayer(p); 596 VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE); 597 ) { 598 final String testName = TEST_NAME + " " + player.name(); 599 600 Log.d(TAG, testName + " starting"); 601 player.start(); 602 Thread.sleep(WARMUP_TIME_MS); 603 604 runDuckTest(testName, volumeShaper); 605 runCloseTest(testName, volumeShaper); 606 } 607 } 608 } // testPlayerDuck 609 610 @LargeTest 611 public void testPlayerRamp() throws Exception { 612 final String TEST_NAME = "testPlayerRamp"; 613 if (!hasAudioOutput()) { 614 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 615 + "audio output HAL"); 616 return; 617 } 618 619 for (int p = 0; p < PLAYER_TYPES; ++p) { 620 try ( Player player = createPlayer(p); 621 VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE); 622 ) { 623 final String testName = TEST_NAME + " " + player.name(); 624 625 Log.d(TAG, testName + " starting"); 626 player.start(); 627 Thread.sleep(WARMUP_TIME_MS); 628 629 runRampTest(testName, volumeShaper); 630 runCloseTest(testName, volumeShaper); 631 } 632 } 633 } // testPlayerRamp 634 635 @LargeTest 636 public void testPlayerCornerCase() throws Exception { 637 final String TEST_NAME = "testPlayerCornerCase"; 638 if (!hasAudioOutput()) { 639 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 640 + "audio output HAL"); 641 return; 642 } 643 644 final VolumeShaper.Configuration config = LINEAR_RAMP; 645 646 for (int p = 0; p < PLAYER_TYPES; ++p) { 647 Player player = null; 648 VolumeShaper volumeShaper = null; 649 try { 650 player = createPlayer(p); 651 volumeShaper = player.createVolumeShaper(config); 652 final String testName = TEST_NAME + " " + player.name(); 653 654 runStartIdleTest(testName, volumeShaper, player); 655 runRampCornerCaseTest(testName, volumeShaper, config); 656 runCloseTest(testName, volumeShaper); 657 658 Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause"); 659 volumeShaper = player.createVolumeShaper(config); 660 player.pause(); 661 Thread.sleep(100 /* millis */); 662 runStartIdleTest(testName, volumeShaper, player); 663 664 // volumeShaper not explicitly closed, will close upon finalize or player close. 665 Log.d(TAG, testName + " recreating VolumeShaper and repeating with stop"); 666 volumeShaper = player.createVolumeShaper(config); 667 player.stop(); 668 Thread.sleep(100 /* millis */); 669 runStartIdleTest(testName, volumeShaper, player); 670 671 Log.d(TAG, testName + " closing Player before VolumeShaper"); 672 player.close(); 673 runCloseTest2(testName, volumeShaper); 674 } finally { 675 if (volumeShaper != null) { 676 volumeShaper.close(); 677 } 678 if (player != null) { 679 player.close(); 680 } 681 } 682 } 683 } // testPlayerCornerCase 684 685 @LargeTest 686 public void testPlayerCornerCase2() throws Exception { 687 final String TEST_NAME = "testPlayerCornerCase2"; 688 if (!hasAudioOutput()) { 689 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 690 + "audio output HAL"); 691 return; 692 } 693 694 final VolumeShaper.Configuration config = LINEAR_RAMP; 695 696 for (int p = 0; p < PLAYER_TYPES; ++p) { 697 Player player = null; 698 VolumeShaper volumeShaper = null; 699 try { 700 player = createPlayer(p); 701 volumeShaper = player.createVolumeShaper(config); 702 final String testName = TEST_NAME + " " + player.name(); 703 704 runStartSyncTest(testName, volumeShaper, player); 705 runCloseTest(testName, volumeShaper); 706 707 Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause"); 708 volumeShaper = player.createVolumeShaper(config); 709 player.pause(); 710 Thread.sleep(100 /* millis */); 711 runStartSyncTest(testName, volumeShaper, player); 712 713 Log.d(TAG, testName + " closing Player before VolumeShaper"); 714 player.close(); 715 runCloseTest2(testName, volumeShaper); 716 } finally { 717 if (volumeShaper != null) { 718 volumeShaper.close(); 719 } 720 if (player != null) { 721 player.close(); 722 } 723 } 724 } 725 } // testPlayerCornerCase2 726 727 @FlakyTest 728 @LargeTest 729 public void testPlayerJoin() throws Exception { 730 final String TEST_NAME = "testPlayerJoin"; 731 if (!hasAudioOutput()) { 732 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 733 + "audio output HAL"); 734 return; 735 } 736 737 for (int p = 0; p < PLAYER_TYPES; ++p) { 738 try ( Player player = createPlayer(p); 739 VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE); 740 ) { 741 final String testName = TEST_NAME + " " + player.name(); 742 volumeShaper.apply(VolumeShaper.Operation.PLAY); 743 player.start(); 744 Thread.sleep(WARMUP_TIME_MS); 745 746 Log.d(TAG, " we join several LINEAR_RAMPS together " 747 + " this effectively is one LINEAR_RAMP (volume increasing)."); 748 final long durationMs = 10000; 749 final long incrementMs = 1000; 750 for (long i = 0; i < durationMs; i += incrementMs) { 751 Log.d(TAG, testName + " Play - join " + i); 752 volumeShaper.replace(new VolumeShaper.Configuration.Builder(LINEAR_RAMP) 753 .setDuration(durationMs - i) 754 .build(), 755 VolumeShaper.Operation.PLAY, true /* join */); 756 assertEquals(testName + " linear ramp should continue on join", 757 (float)i / durationMs, volumeShaper.getVolume(), 0.05 /* epsilon */); 758 Thread.sleep(incrementMs); 759 } 760 Log.d(TAG, testName + "volume at max level now (closing player)"); 761 } 762 } 763 } // testPlayerJoin 764 765 @LargeTest 766 public void testPlayerCubicMonotonic() throws Exception { 767 final String TEST_NAME = "testPlayerCubicMonotonic"; 768 if (!hasAudioOutput()) { 769 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 770 + "audio output HAL"); 771 return; 772 } 773 774 final VolumeShaper.Configuration configurations[] = 775 new VolumeShaper.Configuration[] { 776 MONOTONIC_TEST, 777 CUBIC_RAMP, 778 SCURVE_RAMP, 779 SINE_RAMP, 780 }; 781 782 for (int p = 0; p < PLAYER_TYPES; ++p) { 783 try ( Player player = createPlayer(p); 784 VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE); 785 ) { 786 final String testName = TEST_NAME + " " + player.name(); 787 volumeShaper.apply(VolumeShaper.Operation.PLAY); 788 player.start(); 789 Thread.sleep(WARMUP_TIME_MS); 790 791 for (VolumeShaper.Configuration configuration : configurations) { 792 // test configurations known monotonic 793 Log.d(TAG, testName + " starting test"); 794 795 float lastVolume = 0; 796 final long incrementMs = 100; 797 798 volumeShaper.replace(configuration, 799 VolumeShaper.Operation.PLAY, true /* join */); 800 // monotonicity test 801 for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) { 802 final float volume = volumeShaper.getVolume(); 803 assertTrue(testName + " montonic volume should increase " 804 + volume + " >= " + lastVolume, 805 (volume >= lastVolume)); 806 lastVolume = volume; 807 Thread.sleep(incrementMs); 808 } 809 Thread.sleep(WARMUP_TIME_MS); 810 lastVolume = volumeShaper.getVolume(); 811 assertEquals(testName 812 + " final monotonic value should be 1.f, but is " + lastVolume, 813 1.f, lastVolume, VOLUME_TOLERANCE); 814 815 Log.d(TAG, "invert"); 816 // invert 817 VolumeShaper.Configuration newConfiguration = 818 new VolumeShaper.Configuration.Builder(configuration) 819 .invertVolumes() 820 .build(); 821 volumeShaper.replace(newConfiguration, 822 VolumeShaper.Operation.PLAY, true /* join */); 823 // monotonicity test 824 for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) { 825 final float volume = volumeShaper.getVolume(); 826 assertTrue(testName + " montonic volume should decrease " 827 + volume + " <= " + lastVolume, 828 (volume <= lastVolume)); 829 lastVolume = volume; 830 Thread.sleep(incrementMs); 831 } 832 Thread.sleep(WARMUP_TIME_MS); 833 lastVolume = volumeShaper.getVolume(); 834 assertEquals(testName 835 + " final monotonic value should be 0.f, but is " + lastVolume, 836 0.f, lastVolume, VOLUME_TOLERANCE); 837 838 // invert + reflect 839 Log.d(TAG, "invert and reflect"); 840 newConfiguration = 841 new VolumeShaper.Configuration.Builder(configuration) 842 .invertVolumes() 843 .reflectTimes() 844 .build(); 845 volumeShaper.replace(newConfiguration, 846 VolumeShaper.Operation.PLAY, true /* join */); 847 // monotonicity test 848 for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) { 849 final float volume = volumeShaper.getVolume(); 850 assertTrue(testName + " montonic volume should increase " 851 + volume + " >= " + lastVolume, 852 (volume >= lastVolume - VOLUME_TOLERANCE)); 853 lastVolume = volume; 854 Thread.sleep(incrementMs); 855 } 856 Thread.sleep(WARMUP_TIME_MS); 857 lastVolume = volumeShaper.getVolume(); 858 assertEquals(testName 859 + " final monotonic value should be 1.f, but is " + lastVolume, 860 1.f, lastVolume, VOLUME_TOLERANCE); 861 862 // reflect 863 Log.d(TAG, "reflect"); 864 newConfiguration = 865 new VolumeShaper.Configuration.Builder(configuration) 866 .reflectTimes() 867 .build(); 868 volumeShaper.replace(newConfiguration, 869 VolumeShaper.Operation.PLAY, true /* join */); 870 // monotonicity test 871 for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) { 872 final float volume = volumeShaper.getVolume(); 873 assertTrue(testName + " montonic volume should decrease " 874 + volume + " <= " + lastVolume, 875 (volume <= lastVolume)); 876 lastVolume = volume; 877 Thread.sleep(incrementMs); 878 } 879 Thread.sleep(WARMUP_TIME_MS); 880 lastVolume = volumeShaper.getVolume(); 881 assertEquals(testName 882 + " final monotonic value should be 0.f, but is " + lastVolume, 883 0.f, lastVolume, VOLUME_TOLERANCE); 884 } 885 } 886 } 887 } // testPlayerCubicMonotonic 888 889 @LargeTest 890 public void testPlayerStepRamp() throws Exception { 891 final String TEST_NAME = "testPlayerStepRamp"; 892 if (!hasAudioOutput()) { 893 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 894 + "audio output HAL"); 895 return; 896 } 897 898 // We test that the step ramp persists on value until the next control point. 899 // The STEP_RAMP has only 2 control points (at time 0.f and at 1.f). 900 // It should suddenly jump to full volume at 1.f (full duration). 901 // Note: invertVolumes() and reflectTimes() are not symmetric for STEP interpolation; 902 // however, VolumeShaper.Operation.REVERSE will behave symmetrically. 903 for (int p = 0; p < PLAYER_TYPES; ++p) { 904 try ( Player player = createPlayer(p); 905 VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE); 906 ) { 907 final String testName = TEST_NAME + " " + player.name(); 908 volumeShaper.apply(VolumeShaper.Operation.PLAY); 909 player.start(); 910 Thread.sleep(WARMUP_TIME_MS); 911 912 final VolumeShaper.Configuration configuration = STEP_RAMP; 913 Log.d(TAG, testName + " starting test (sudden jump to full after " 914 + RAMP_TIME_MS + " milliseconds)"); 915 916 volumeShaper.replace(configuration, 917 VolumeShaper.Operation.PLAY, true /* join */); 918 919 Thread.sleep(RAMP_TIME_MS / 2); 920 float lastVolume = volumeShaper.getVolume(); 921 assertEquals(testName 922 + " middle value should be 0.f, but is " + lastVolume, 923 0.f, lastVolume, VOLUME_TOLERANCE); 924 925 Thread.sleep(RAMP_TIME_MS / 2 + 1000); 926 lastVolume = volumeShaper.getVolume(); 927 assertEquals(testName 928 + " final value should be 1.f, but is " + lastVolume, 929 1.f, lastVolume, VOLUME_TOLERANCE); 930 931 Log.d(TAG, "invert (sudden jump to silence after " 932 + RAMP_TIME_MS + " milliseconds)"); 933 // invert 934 VolumeShaper.Configuration newConfiguration = 935 new VolumeShaper.Configuration.Builder(configuration) 936 .invertVolumes() 937 .build(); 938 volumeShaper.replace(newConfiguration, 939 VolumeShaper.Operation.PLAY, true /* join */); 940 941 Thread.sleep(RAMP_TIME_MS / 2); 942 lastVolume = volumeShaper.getVolume(); 943 assertEquals(testName 944 + " middle value should be 1.f, but is " + lastVolume, 945 1.f, lastVolume, VOLUME_TOLERANCE); 946 947 Thread.sleep(RAMP_TIME_MS / 2 + 1000); 948 lastVolume = volumeShaper.getVolume(); 949 assertEquals(testName 950 + " final value should be 0.f, but is " + lastVolume, 951 0.f, lastVolume, VOLUME_TOLERANCE); 952 953 // invert + reflect 954 Log.d(TAG, "invert and reflect (sudden jump to full after " 955 + RAMP_TIME_MS + " milliseconds)"); 956 newConfiguration = 957 new VolumeShaper.Configuration.Builder(configuration) 958 .invertVolumes() 959 .reflectTimes() 960 .build(); 961 volumeShaper.replace(newConfiguration, 962 VolumeShaper.Operation.PLAY, true /* join */); 963 964 Thread.sleep(RAMP_TIME_MS / 2); 965 lastVolume = volumeShaper.getVolume(); 966 assertEquals(testName 967 + " middle value should be 0.f, but is " + lastVolume, 968 0.f, lastVolume, VOLUME_TOLERANCE); 969 970 Thread.sleep(RAMP_TIME_MS / 2 + 1000); 971 lastVolume = volumeShaper.getVolume(); 972 assertEquals(testName 973 + " final value should be 1.f, but is " + lastVolume, 974 1.f, lastVolume, VOLUME_TOLERANCE); 975 976 // reflect 977 Log.d(TAG, "reflect (sudden jump to silence after " 978 + RAMP_TIME_MS + " milliseconds)"); 979 newConfiguration = 980 new VolumeShaper.Configuration.Builder(configuration) 981 .reflectTimes() 982 .build(); 983 volumeShaper.replace(newConfiguration, 984 VolumeShaper.Operation.PLAY, true /* join */); 985 986 Thread.sleep(RAMP_TIME_MS / 2); 987 lastVolume = volumeShaper.getVolume(); 988 assertEquals(testName 989 + " middle value should be 1.f, but is " + lastVolume, 990 1.f, lastVolume, VOLUME_TOLERANCE); 991 992 Thread.sleep(RAMP_TIME_MS / 2 + 1000); 993 lastVolume = volumeShaper.getVolume(); 994 assertEquals(testName 995 + " final value should be 0.f, but is " + lastVolume, 996 0.f, lastVolume, VOLUME_TOLERANCE); 997 998 Log.d(TAG, "reverse (immediate jump to full)"); 999 volumeShaper.apply(VolumeShaper.Operation.REVERSE); 1000 Thread.sleep(RAMP_TIME_MS / 2); 1001 lastVolume = volumeShaper.getVolume(); 1002 assertEquals(testName 1003 + " middle value should be 1.f, but is " + lastVolume, 1004 1.f, lastVolume, VOLUME_TOLERANCE); 1005 1006 Thread.sleep(RAMP_TIME_MS / 2 + 1000); 1007 lastVolume = volumeShaper.getVolume(); 1008 assertEquals(testName 1009 + " final value should be 1.f, but is " + lastVolume, 1010 1.f, lastVolume, VOLUME_TOLERANCE); 1011 } 1012 } 1013 } // testPlayerStepRamp 1014 1015 @LargeTest 1016 public void testPlayerTwoShapers() throws Exception { 1017 final String TEST_NAME = "testPlayerTwoShapers"; 1018 if (!hasAudioOutput()) { 1019 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 1020 + "audio output HAL"); 1021 return; 1022 } 1023 1024 final long durationMs = 10000; 1025 1026 // Ramp configurations go from 0.f up to 1.f, Duck from 1.f to 0.f 1027 // With the two ramps combined, the audio should rise and then fall. 1028 final VolumeShaper.Configuration LONG_RAMP = 1029 new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP) 1030 .setDuration(durationMs) 1031 .build(); 1032 final VolumeShaper.Configuration LONG_DUCK = 1033 new VolumeShaper.Configuration.Builder(LONG_RAMP) 1034 .reflectTimes() 1035 .build(); 1036 1037 for (int p = 0; p < PLAYER_TYPES; ++p) { 1038 try ( Player player = createPlayer(p); 1039 VolumeShaper volumeShaperRamp = player.createVolumeShaper(LONG_RAMP); 1040 VolumeShaper volumeShaperDuck = player.createVolumeShaper(LONG_DUCK); 1041 ) { 1042 final String testName = TEST_NAME + " " + player.name(); 1043 1044 final float firstVolumeRamp = volumeShaperRamp.getVolume(); 1045 final float firstVolumeDuck = volumeShaperDuck.getVolume(); 1046 assertEquals(testName 1047 + " first ramp value should be 0.f, but is " + firstVolumeRamp, 1048 0.f, firstVolumeRamp, VOLUME_TOLERANCE); 1049 assertEquals(testName 1050 + " first duck value should be 1.f, but is " + firstVolumeDuck, 1051 1.f, firstVolumeDuck, VOLUME_TOLERANCE); 1052 player.start(); 1053 1054 Thread.sleep(1000); 1055 1056 final float lastVolumeRamp = volumeShaperRamp.getVolume(); 1057 final float lastVolumeDuck = volumeShaperDuck.getVolume(); 1058 assertEquals(testName 1059 + " no-play ramp value should be 0.f, but is " + lastVolumeRamp, 1060 0.f, lastVolumeRamp, VOLUME_TOLERANCE); 1061 assertEquals(testName 1062 + " no-play duck value should be 1.f, but is " + lastVolumeDuck, 1063 1.f, lastVolumeDuck, VOLUME_TOLERANCE); 1064 1065 Log.d(TAG, testName + " volume should be silent and start increasing now"); 1066 1067 // we actually start now! 1068 volumeShaperRamp.apply(VolumeShaper.Operation.PLAY); 1069 volumeShaperDuck.apply(VolumeShaper.Operation.PLAY); 1070 Thread.sleep(durationMs / 2); 1071 1072 Log.d(TAG, testName + " volume should be > 0 and about maximum here"); 1073 final float lastVolumeRamp2 = volumeShaperRamp.getVolume(); 1074 final float lastVolumeDuck2 = volumeShaperDuck.getVolume(); 1075 assertTrue(testName 1076 + " last ramp value should be > 0.f " + lastVolumeRamp2, 1077 lastVolumeRamp2 > 0.f); 1078 assertTrue(testName 1079 + " last duck value should be < 1.f " + lastVolumeDuck2, 1080 lastVolumeDuck2 < 1.f); 1081 1082 Log.d(TAG, testName + " volume should start decreasing shortly"); 1083 Thread.sleep(durationMs / 2 + 1000); 1084 1085 Log.d(TAG, testName + " volume should be silent now"); 1086 final float lastVolumeRamp3 = volumeShaperRamp.getVolume(); 1087 final float lastVolumeDuck3 = volumeShaperDuck.getVolume(); 1088 assertEquals(testName 1089 + " last ramp value should be 1.f, but is " + lastVolumeRamp3, 1090 1.f, lastVolumeRamp3, VOLUME_TOLERANCE); 1091 assertEquals(testName 1092 + " last duck value should be 0.f, but is " + lastVolumeDuck3, 1093 0.f, lastVolumeDuck3, VOLUME_TOLERANCE); 1094 1095 runCloseTest(testName, volumeShaperRamp); 1096 runCloseTest(testName, volumeShaperDuck); 1097 } 1098 } 1099 } // testPlayerTwoShapers 1100 1101 // tests that shaper advances in the presence of pause and stop (time based after start). 1102 @LargeTest 1103 public void testPlayerRunDuringPauseStop() throws Exception { 1104 final String TEST_NAME = "testPlayerRunDuringPauseStop"; 1105 if (!hasAudioOutput()) { 1106 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 1107 + "audio output HAL"); 1108 return; 1109 } 1110 1111 final VolumeShaper.Configuration config = LINEAR_RAMP; 1112 1113 for (int p = 0; p < PLAYER_TYPES; ++p) { 1114 for (int pause = 0; pause < 2; ++pause) { 1115 1116 if ((p == PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED 1117 || p == PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED) && pause == 0) { 1118 // Do not test stop and MediaPlayer because a 1119 // MediaPlayer stop requires prepare before starting. 1120 continue; 1121 } 1122 1123 try ( Player player = createPlayer(p); 1124 VolumeShaper volumeShaper = player.createVolumeShaper(config); 1125 ) { 1126 final String testName = TEST_NAME + " " + player.name(); 1127 1128 Log.d(TAG, testName + " starting volume, should ramp up"); 1129 volumeShaper.apply(VolumeShaper.Operation.PLAY); 1130 assertEquals(testName + " volume should be 0.f", 1131 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1132 1133 player.start(); 1134 Thread.sleep(WARMUP_TIME_MS * 2); 1135 1136 Log.d(TAG, testName + " applying " + (pause != 0 ? "pause" : "stop")); 1137 if (pause == 1) { 1138 player.pause(); 1139 } else { 1140 player.stop(); 1141 } 1142 Thread.sleep(RAMP_TIME_MS); 1143 1144 Log.d(TAG, testName + " starting again"); 1145 player.start(); 1146 Thread.sleep(WARMUP_TIME_MS * 2); 1147 1148 Log.d(TAG, testName + " should be full volume"); 1149 assertEquals(testName + " volume should be 1.f", 1150 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1151 } 1152 } 1153 } 1154 } // testPlayerRunDuringPauseStop 1155 1156 // Player should be started before calling (as it is not an argument to method). 1157 private void runRampTest(String testName, VolumeShaper volumeShaper) throws Exception { 1158 for (VolumeShaper.Configuration config : ALL_STANDARD_RAMPS) { 1159 // This replaces with play. 1160 Log.d(TAG, testName + " Replace + Play (volume should increase)"); 1161 volumeShaper.replace(config, VolumeShaper.Operation.PLAY, false /* join */); 1162 Thread.sleep(RAMP_TIME_MS / 2); 1163 1164 // Reverse the direction of the volume shaper curve 1165 Log.d(TAG, testName + " Reverse (volume should decrease)"); 1166 volumeShaper.apply(VolumeShaper.Operation.REVERSE); 1167 Thread.sleep(RAMP_TIME_MS / 2 + 1000); 1168 1169 Log.d(TAG, testName + " Check Volume (silent)"); 1170 assertEquals(testName + " volume should be 0.f", 1171 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1172 1173 // Forwards 1174 Log.d(TAG, testName + " Play (volume should increase)"); 1175 volumeShaper.apply(VolumeShaper.Operation.PLAY); 1176 Thread.sleep(RAMP_TIME_MS + 1000); 1177 1178 Log.d(TAG, testName + " Check Volume (volume at max)"); 1179 assertEquals(testName + " volume should be 1.f", 1180 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1181 1182 // Reverse 1183 Log.d(TAG, testName + " Reverse (volume should decrease)"); 1184 volumeShaper.apply(VolumeShaper.Operation.REVERSE); 1185 Thread.sleep(RAMP_TIME_MS + 1000); 1186 1187 Log.d(TAG, testName + " Check Volume (volume should be silent)"); 1188 assertEquals(testName + " volume should be 0.f", 1189 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1190 1191 // Forwards 1192 Log.d(TAG, testName + " Play (volume should increase)"); 1193 volumeShaper.apply(VolumeShaper.Operation.PLAY); 1194 Thread.sleep(RAMP_TIME_MS + 1000); 1195 1196 // Comment out for headset plug/unplug test 1197 // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */); 1198 // 1199 1200 Log.d(TAG, testName + " Check Volume (volume at max)"); 1201 assertEquals(testName + " volume should be 1.f", 1202 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1203 1204 Log.d(TAG, testName + " done"); 1205 } 1206 } // runRampTest 1207 1208 // Player should be started before calling (as it is not an argument to method). 1209 private void runDuckTest(String testName, VolumeShaper volumeShaper) throws Exception { 1210 final VolumeShaper.Configuration[] configs = new VolumeShaper.Configuration[] { 1211 LINEAR_DUCK, 1212 }; 1213 1214 for (VolumeShaper.Configuration config : configs) { 1215 Log.d(TAG, testName + " Replace + Reverse (volume at max)"); 1216 // CORNER CASE: When you replace with REVERSE, it stays at the initial point. 1217 volumeShaper.replace(config, VolumeShaper.Operation.REVERSE, false /* join */); 1218 Thread.sleep(RAMP_TIME_MS / 2); 1219 assertEquals(testName + " volume should be 1.f", 1220 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1221 1222 // CORNER CASE: reverse twice doesn't do anything. 1223 Thread.sleep(RAMP_TIME_MS / 2); 1224 Log.d(TAG, testName + " Reverse after reverse (volume at max)"); 1225 volumeShaper.apply(VolumeShaper.Operation.REVERSE); 1226 assertEquals(testName + " volume should be 1.f", 1227 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1228 1229 Log.d(TAG, testName + " Duck from start (volume should decrease)"); 1230 volumeShaper.apply(VolumeShaper.Operation.PLAY); 1231 Thread.sleep(RAMP_TIME_MS * 2); 1232 1233 Log.d(TAG, testName + " Duck done (volume should be low, 0.2f)"); 1234 assertEquals(testName + " volume should be 0.2f", 1235 0.2f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1236 1237 Log.d(TAG, testName + " Unduck (volume should increase)"); 1238 volumeShaper.apply(VolumeShaper.Operation.REVERSE); 1239 Thread.sleep(RAMP_TIME_MS * 2); 1240 1241 // Comment out for headset plug/unplug test 1242 // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */); 1243 // 1244 Log.d(TAG, testName + " Unduck done (volume at max)"); 1245 assertEquals(testName + " volume should be 1.f", 1246 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1247 } 1248 } // runDuckTest 1249 1250 // VolumeShaper should not be started prior to this test; it is replaced 1251 // in this test. 1252 private void runRampCornerCaseTest( 1253 String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config) 1254 throws Exception { 1255 // ramps start at 0.f 1256 assertEquals(testName + " volume should be 0.f", 1257 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1258 1259 Log.d(TAG, testName + " Reverse at start (quiet now)"); 1260 // CORNER CASE: When you begin with REVERSE, it stays at the initial point. 1261 volumeShaper.apply(VolumeShaper.Operation.REVERSE); 1262 Thread.sleep(RAMP_TIME_MS / 2); 1263 assertEquals(testName + " volume should be 0.f", 1264 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1265 1266 // CORNER CASE: reverse twice doesn't do anything. 1267 Thread.sleep(RAMP_TIME_MS / 2); 1268 Log.d(TAG, testName + " Reverse after reverse (still quiet)"); 1269 volumeShaper.apply(VolumeShaper.Operation.REVERSE); 1270 assertEquals(testName + " volume should be 0.f", 1271 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1272 1273 Log.d(TAG, testName + " Ramp from start (volume should increase)"); 1274 volumeShaper.apply(VolumeShaper.Operation.PLAY); 1275 Thread.sleep(RAMP_TIME_MS * 2); 1276 1277 Log.d(TAG, testName + " Volume persists at maximum 1.f"); 1278 assertEquals(testName + " volume should be 1.f", 1279 1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1280 1281 Log.d(TAG, testName + " Reverse ramp (volume should decrease)"); 1282 volumeShaper.apply(VolumeShaper.Operation.REVERSE); 1283 Thread.sleep(RAMP_TIME_MS / 2); 1284 1285 // join in REVERSE should freeze 1286 final float volume = volumeShaper.getVolume(); 1287 Log.d(TAG, testName + " Replace ramp with join in REVERSE (volume steady)"); 1288 volumeShaper.replace(config, VolumeShaper.Operation.REVERSE, true /* join */); 1289 1290 Thread.sleep(RAMP_TIME_MS / 2); 1291 // Are we frozen? 1292 final float volume2 = volumeShaper.getVolume(); 1293 assertEquals(testName + " volume should be the same (volume steady)", 1294 volume, volume2, JOIN_VOLUME_TOLERANCE); 1295 1296 // Begin playing 1297 Log.d(TAG, testName + " Play joined ramp (volume should increase)"); 1298 volumeShaper.apply(VolumeShaper.Operation.PLAY); 1299 Thread.sleep(RAMP_TIME_MS * 2); 1300 1301 // Reverse to get back to start of the joined curve. 1302 Log.d(TAG, testName + " Reverse joined ramp (volume should decrease)"); 1303 volumeShaper.apply(VolumeShaper.Operation.REVERSE); 1304 Thread.sleep(RAMP_TIME_MS * 2); 1305 1306 // At this time, we are back to the join point. 1307 // We check now that the scaling for the join is permanent. 1308 Log.d(TAG, testName + " Joined ramp at start (volume same as at join)"); 1309 final float volume3 = volumeShaper.getVolume(); 1310 assertEquals(testName + " volume should be same as start for joined ramp", 1311 volume2, volume3, JOIN_VOLUME_TOLERANCE); 1312 } // runRampCornerCaseTest 1313 1314 // volumeShaper is closed in this test. 1315 private void runCloseTest(String testName, VolumeShaper volumeShaper) throws Exception { 1316 Log.d(TAG, testName + " closing"); 1317 volumeShaper.close(); 1318 runCloseTest2(testName, volumeShaper); 1319 } // runCloseTest 1320 1321 // VolumeShaper should be closed prior to this test. 1322 private void runCloseTest2(String testName, VolumeShaper volumeShaper) throws Exception { 1323 // CORNER CASE: 1324 // VolumeShaper methods should throw ISE after closing. 1325 Log.d(TAG, testName + " getVolume() after close should throw ISE"); 1326 assertThrows(IllegalStateException.class, 1327 volumeShaper::getVolume); 1328 1329 Log.d(TAG, testName + " apply() after close should throw ISE"); 1330 assertThrows(IllegalStateException.class, 1331 ()->{ volumeShaper.apply(VolumeShaper.Operation.REVERSE); }); 1332 1333 Log.d(TAG, testName + " replace() after close should throw ISE"); 1334 assertThrows(IllegalStateException.class, 1335 ()->{ volumeShaper.replace( 1336 LINEAR_RAMP, VolumeShaper.Operation.PLAY, false /* join */); }); 1337 1338 Log.d(TAG, testName + " closing x2 is OK"); 1339 volumeShaper.close(); // OK to close twice. 1340 Log.d(TAG, testName + " closing x3 is OK"); 1341 volumeShaper.close(); // OK to close thrice. 1342 } // runCloseTest2 1343 1344 // Player should not be started prior to calling (it is started in this test) 1345 // VolumeShaper should not be started prior to calling (it is not started in this test). 1346 private void runStartIdleTest(String testName, VolumeShaper volumeShaper, Player player) 1347 throws Exception { 1348 Log.d(TAG, testName + " volume after creation or pause doesn't advance (silent now)"); 1349 // CORNER CASE: 1350 // volumeShaper volume after creation or pause doesn't advance. 1351 Thread.sleep(WARMUP_TIME_MS); 1352 assertEquals(testName + " volume should be 0.f", 1353 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1354 1355 player.start(); 1356 Thread.sleep(WARMUP_TIME_MS); 1357 1358 Log.d(TAG, testName + " volume after player start doesn't advance if play isn't called." 1359 + " (still silent)"); 1360 // CORNER CASE: 1361 // volumeShaper volume after creation doesn't or pause doesn't advance even 1362 // after the player starts. 1363 Thread.sleep(WARMUP_TIME_MS); 1364 assertEquals(testName + " volume should be 0.f", 1365 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1366 } // runStartIdleTest 1367 1368 // Player should not be running prior to calling (it is started in this test). 1369 // VolumeShaper is also started in this test. 1370 private void runStartSyncTest(String testName, VolumeShaper volumeShaper, Player player) 1371 throws Exception { 1372 Log.d(TAG, testName + " volume after creation or pause doesn't advance " 1373 + "if player isn't started. (silent now)"); 1374 volumeShaper.apply(VolumeShaper.Operation.PLAY); 1375 // CORNER CASE: 1376 // volumeShaper volume after creation or pause doesn't advance 1377 // even after play is called. 1378 Thread.sleep(WARMUP_TIME_MS); 1379 assertEquals(testName + " volume should be 0.f", 1380 0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE); 1381 1382 Log.d(TAG, testName + " starting now (volume should increase)"); 1383 player.start(); 1384 Thread.sleep(WARMUP_TIME_MS); 1385 1386 Log.d(TAG, testName + " volume after player start advances if play is called."); 1387 // CORNER CASE: 1388 // Now volume should have advanced since play is called. 1389 Thread.sleep(WARMUP_TIME_MS); 1390 assertTrue(testName + " volume should be greater than 0.f", 1391 volumeShaper.getVolume() > 0.f); 1392 } // runStartSyncTest 1393 } 1394