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