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