1 /* 2 * Copyright (C) 2010 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 org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertThrows; 22 import static org.junit.Assert.assertTrue; 23 import static org.junit.Assert.fail; 24 25 import android.content.Context; 26 import android.media.AudioManager; 27 import android.media.MediaPlayer; 28 import android.media.audio.cts.R; 29 import android.media.audiofx.AudioEffect; 30 import android.media.audiofx.Visualizer; 31 import android.media.audiofx.Visualizer.MeasurementPeakRms; 32 import android.os.Looper; 33 import android.platform.test.annotations.AppModeFull; 34 import android.util.Log; 35 36 import androidx.test.runner.AndroidJUnit4; 37 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 41 import java.util.UUID; 42 43 @AppModeFull(reason = "TODO: evaluate and port to instant") 44 @RunWith(AndroidJUnit4.class) 45 public class VisualizerTest extends PostProcTestBase { 46 47 private String TAG = "VisualizerTest"; 48 private final static int MIN_CAPTURE_RATE_MAX = 10000; // 10Hz 49 private final static int MIN_CAPTURE_SIZE_MAX = 1024; 50 private final static int MAX_CAPTURE_SIZE_MIN = 512; 51 private final static int MAX_LOOPER_WAIT_COUNT = 10; 52 53 private Visualizer mVisualizer = null; 54 private byte[] mWaveform = null; 55 private byte[] mFft = null; 56 private boolean mCaptureWaveform = false; 57 private boolean mCaptureFft = false; 58 private Thread mListenerThread; 59 60 //----------------------------------------------------------------- 61 // VISUALIZER TESTS: 62 //---------------------------------- 63 64 //----------------------------------------------------------------- 65 // 0 - constructor 66 //---------------------------------- 67 68 //Test case 0.0: test constructor and release 69 @Test test0_0ConstructorAndRelease()70 public void test0_0ConstructorAndRelease() throws Exception { 71 Visualizer visualizer = null; 72 try { 73 visualizer = new Visualizer(0); 74 } catch (IllegalArgumentException e) { 75 fail("Visualizer not found"); 76 } catch (UnsupportedOperationException e) { 77 fail("Effect library not loaded"); 78 } finally { 79 if (visualizer != null) { 80 visualizer.release(); 81 } 82 } 83 } 84 85 86 //----------------------------------------------------------------- 87 // 1 - get/set parameters 88 //---------------------------------- 89 90 //Test case 1.0: capture rates 91 @Test test1_0CaptureRates()92 public void test1_0CaptureRates() throws Exception { 93 getVisualizer(0); 94 try { 95 int captureRate = mVisualizer.getMaxCaptureRate(); 96 assertTrue("insufficient max capture rate", 97 captureRate >= MIN_CAPTURE_RATE_MAX); 98 int samplingRate = mVisualizer.getSamplingRate(); 99 } catch (IllegalArgumentException e) { 100 fail("Bad parameter value"); 101 } catch (UnsupportedOperationException e) { 102 fail("get parameter() rejected"); 103 } catch (IllegalStateException e) { 104 fail("get parameter() called in wrong state"); 105 } finally { 106 releaseVisualizer(); 107 } 108 } 109 110 //Test case 1.1: test capture size 111 @Test test1_1CaptureSize()112 public void test1_1CaptureSize() throws Exception { 113 getVisualizer(0); 114 try { 115 int[] range = mVisualizer.getCaptureSizeRange(); 116 assertTrue("insufficient min capture size", 117 range[0] <= MAX_CAPTURE_SIZE_MIN); 118 assertTrue("insufficient max capture size", 119 range[1] >= MIN_CAPTURE_SIZE_MAX); 120 int size = mVisualizer.getCaptureSize(); 121 assertTrue("capture size smaller than min", 122 size >= range[0]); 123 assertTrue("capture size larger than max", 124 size <= range[1]); 125 mVisualizer.setCaptureSize(range[0]); 126 assertEquals("insufficient min capture size", 127 range[0], mVisualizer.getCaptureSize()); 128 mVisualizer.setCaptureSize(range[1]); 129 assertEquals("insufficient max capture size", 130 range[1], mVisualizer.getCaptureSize()); 131 } catch (IllegalArgumentException e) { 132 fail("Bad parameter value"); 133 } catch (UnsupportedOperationException e) { 134 fail("get parameter() rejected"); 135 } catch (IllegalStateException e) { 136 fail("get parameter() called in wrong state"); 137 } finally { 138 releaseVisualizer(); 139 } 140 } 141 142 //Test case 1.2: test setting illegal capture size and expect IllegalArgumentException 143 @Test test1_2SetIllegalCaptureSize()144 public void test1_2SetIllegalCaptureSize() throws Exception { 145 getVisualizer(0); 146 int[] range = mVisualizer.getCaptureSizeRange(); 147 assertTrue("insufficient min capture size", 148 range[0] <= MAX_CAPTURE_SIZE_MIN); 149 assertTrue("insufficient max capture size", 150 range[1] >= MIN_CAPTURE_SIZE_MAX); 151 152 assertThrows(IllegalArgumentException.class, 153 () -> mVisualizer.setCaptureSize(range[0] - 1)); 154 assertThrows(IllegalArgumentException.class, 155 () -> mVisualizer.setCaptureSize(range[1] + 1)); 156 } 157 158 //----------------------------------------------------------------- 159 // 2 - check capture 160 //---------------------------------- 161 162 //Test case 2.0: test capture in polling mode 163 @Test test2_0PollingCapture()164 public void test2_0PollingCapture() throws Exception { 165 if (!hasAudioOutput()) { 166 Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid " 167 + "audio output HAL"); 168 return; 169 } 170 try { 171 getVisualizer(0); 172 mVisualizer.setEnabled(true); 173 assertTrue("visualizer not enabled", mVisualizer.getEnabled()); 174 Thread.sleep(100); 175 // check capture on silence 176 byte[] data = new byte[mVisualizer.getCaptureSize()]; 177 mVisualizer.getWaveForm(data); 178 int energy = computeEnergy(data, true); 179 assertEquals("getWaveForm reports energy for silence", 180 0, energy); 181 mVisualizer.getFft(data); 182 energy = computeEnergy(data, false); 183 assertEquals("getFft reports energy for silence", 184 0, energy); 185 186 } catch (IllegalStateException e) { 187 fail("method called in wrong state"); 188 } catch (InterruptedException e) { 189 fail("sleep() interrupted"); 190 } finally { 191 releaseVisualizer(); 192 } 193 } 194 195 //Test case 2.1: test capture with listener 196 @Test test2_1ListenerCapture()197 public void test2_1ListenerCapture() throws Exception { 198 if (!hasAudioOutput()) { 199 Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid " 200 + "audio output HAL"); 201 return; 202 } 203 try { 204 getVisualizer(0); 205 synchronized(mLock) { 206 mInitialized = false; 207 createListenerLooper(); 208 waitForLooperInitialization_l(); 209 } 210 mVisualizer.setEnabled(true); 211 assertTrue("visualizer not enabled", mVisualizer.getEnabled()); 212 213 Thread.sleep(100); 214 215 // check capture on silence 216 synchronized(mLock) { 217 mCaptureWaveform = true; 218 int looperWaitCount = MAX_LOOPER_WAIT_COUNT; 219 while ((mWaveform == null) && (looperWaitCount-- > 0)) { 220 try { 221 mLock.wait(); 222 } catch(Exception e) { 223 } 224 } 225 mCaptureWaveform = false; 226 } 227 assertNotNull("waveform capture failed", mWaveform); 228 int energy = computeEnergy(mWaveform, true); 229 assertEquals("getWaveForm reports energy for silence", 230 0, energy); 231 232 synchronized(mLock) { 233 mCaptureFft = true; 234 int looperWaitCount = MAX_LOOPER_WAIT_COUNT; 235 while ((mFft == null) && (looperWaitCount-- > 0)) { 236 try { 237 mLock.wait(); 238 } catch(Exception e) { 239 } 240 } 241 mCaptureFft = false; 242 } 243 assertNotNull("FFT capture failed", mFft); 244 energy = computeEnergy(mFft, false); 245 assertEquals("getFft reports energy for silence", 246 0, energy); 247 248 } catch (IllegalStateException e) { 249 fail("method called in wrong state"); 250 } catch (InterruptedException e) { 251 fail("sleep() interrupted"); 252 } finally { 253 terminateListenerLooper(); 254 releaseVisualizer(); 255 } 256 } 257 258 //Test case 2.2: test capture with illegal size 259 @Test test2_2IllegalCaptureSize()260 public void test2_2IllegalCaptureSize() throws Exception { 261 if (!hasAudioOutput()) { 262 Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " 263 + "audio output HAL"); 264 return; 265 } 266 try { 267 getVisualizer(0); 268 mVisualizer.setEnabled(true); 269 assertTrue("visualizer not enabled", mVisualizer.getEnabled()); 270 int captureSize = mVisualizer.getCaptureSize(); 271 if (captureSize <= 0) { 272 Log.w(TAG, "This system visualizer doesn't support capture waveform"); 273 return; 274 } 275 byte[] data = new byte[captureSize - 1]; 276 mVisualizer.getWaveForm(data); 277 fail("Should have thrown IllegalArgumentException"); 278 } catch (IllegalArgumentException e) { 279 // success 280 } 281 } 282 283 //----------------------------------------------------------------- 284 // 3 - check measurement mode MEASUREMENT_MODE_NONE 285 //---------------------------------- 286 287 //Test case 3.0: test setting NONE measurement mode 288 @Test test3_0MeasurementModeNone()289 public void test3_0MeasurementModeNone() throws Exception { 290 if (!hasAudioOutput()) { 291 return; 292 } 293 try { 294 getVisualizer(0); 295 mVisualizer.setEnabled(true); 296 assertTrue("visualizer not enabled", mVisualizer.getEnabled()); 297 Thread.sleep(100); 298 299 int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_NONE); 300 assertEquals("setMeasurementMode for NONE doesn't report success", 301 Visualizer.SUCCESS, status); 302 303 int mode = mVisualizer.getMeasurementMode(); 304 assertEquals("getMeasurementMode reports NONE", 305 Visualizer.MEASUREMENT_MODE_NONE, mode); 306 307 } catch (IllegalStateException e) { 308 fail("method called in wrong state"); 309 } catch (InterruptedException e) { 310 fail("sleep() interrupted"); 311 } finally { 312 releaseVisualizer(); 313 } 314 } 315 316 //----------------------------------------------------------------- 317 // 4 - check measurement mode MEASUREMENT_MODE_PEAK_RMS 318 //---------------------------------- 319 320 //Test case 4.0: test setting peak / RMS measurement mode 321 @Test test4_0MeasurementModePeakRms()322 public void test4_0MeasurementModePeakRms() throws Exception { 323 if (!hasAudioOutput()) { 324 return; 325 } 326 try { 327 getVisualizer(0); 328 mVisualizer.setEnabled(true); 329 assertTrue("visualizer not enabled", mVisualizer.getEnabled()); 330 Thread.sleep(100); 331 332 int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS); 333 assertEquals("setMeasurementMode for PEAK_RMS doesn't report success", 334 Visualizer.SUCCESS, status); 335 336 int mode = mVisualizer.getMeasurementMode(); 337 assertEquals("getMeasurementMode doesn't report PEAK_RMS", 338 Visualizer.MEASUREMENT_MODE_PEAK_RMS, mode); 339 340 } catch (IllegalStateException e) { 341 fail("method called in wrong state"); 342 } catch (InterruptedException e) { 343 fail("sleep() interrupted"); 344 } finally { 345 releaseVisualizer(); 346 } 347 } 348 349 //Test case 4.1: test measurement of peak / RMS 350 @Test test4_1MeasurePeakRms()351 public void test4_1MeasurePeakRms() throws Exception { 352 if (!hasAudioOutput()) { 353 return; 354 } 355 AudioEffect vc = null; 356 try { 357 // this test will play a 1kHz sine wave with peaks at -40dB 358 MediaPlayer mp = MediaPlayer.create(getContext(), R.raw.sine1khzm40db); 359 final int EXPECTED_PEAK_MB = -4015; 360 final int EXPECTED_RMS_MB = -4300; 361 final int MAX_MEASUREMENT_ERROR_MB = 2000; 362 assertNotNull("null MediaPlayer", mp); 363 364 // creating a volume controller on output mix ensures that ro.audio.silent mutes 365 // audio after the effects and not before 366 vc = new AudioEffect( 367 AudioEffect.EFFECT_TYPE_NULL, 368 UUID.fromString(BUNDLE_VOLUME_EFFECT_UUID), 369 0, 370 mp.getAudioSessionId()); 371 vc.setEnabled(true); 372 373 AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 374 assertNotNull("null AudioManager", am); 375 int originalVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC); 376 am.setStreamVolume(AudioManager.STREAM_MUSIC, 377 am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0); 378 getVisualizer(mp.getAudioSessionId()); 379 mp.setLooping(true); 380 mp.start(); 381 382 mVisualizer.setEnabled(true); 383 assertTrue("visualizer not enabled", mVisualizer.getEnabled()); 384 Thread.sleep(100); 385 int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS); 386 assertEquals("setMeasurementMode() for PEAK_RMS doesn't report success", 387 Visualizer.SUCCESS, status); 388 // make sure we're playing long enough so the measurement is valid 389 int currentPosition = mp.getCurrentPosition(); 390 final int maxTry = 100; 391 int tryCount = 0; 392 while (currentPosition < 200 && tryCount < maxTry) { 393 Thread.sleep(50); 394 currentPosition = mp.getCurrentPosition(); 395 tryCount++; 396 } 397 assertTrue("MediaPlayer not ready", tryCount < maxTry); 398 399 MeasurementPeakRms measurement = new MeasurementPeakRms(); 400 status = mVisualizer.getMeasurementPeakRms(measurement); 401 mp.stop(); 402 mp.release(); 403 am.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0); 404 assertEquals("getMeasurementPeakRms() reports failure", 405 Visualizer.SUCCESS, status); 406 Log.i("VisTest", "peak="+measurement.mPeak+" rms="+measurement.mRms); 407 int deltaPeak = Math.abs(measurement.mPeak - EXPECTED_PEAK_MB); 408 int deltaRms = Math.abs(measurement.mRms - EXPECTED_RMS_MB); 409 assertTrue("peak deviation in mB=" + deltaPeak, deltaPeak < MAX_MEASUREMENT_ERROR_MB); 410 assertTrue("RMS deviation in mB=" + deltaRms, deltaRms < MAX_MEASUREMENT_ERROR_MB); 411 412 } catch (IllegalStateException e) { 413 fail("method called in wrong state"); 414 } catch (InterruptedException e) { 415 fail("sleep() interrupted"); 416 } finally { 417 if (vc != null) 418 vc.release(); 419 releaseVisualizer(); 420 } 421 } 422 423 //Test case 4.2: test measurement of peak / RMS in Long MP3 424 @Test test4_2MeasurePeakRmsLongMP3()425 public void test4_2MeasurePeakRmsLongMP3() throws Exception { 426 if (!hasAudioOutput()) { 427 return; 428 } 429 AudioEffect vc = null; 430 try { 431 // this test will play a 1kHz sine wave with peaks at -40dB 432 MediaPlayer mp = MediaPlayer.create(getContext(), R.raw.sine1khzs40dblong); 433 final int EXPECTED_PEAK_MB = -4015; 434 final int EXPECTED_RMS_MB = -4300; 435 final int MAX_MEASUREMENT_ERROR_MB = 2000; 436 assertNotNull("null MediaPlayer", mp); 437 438 // creating a volume controller on output mix ensures that ro.audio.silent mutes 439 // audio after the effects and not before 440 vc = new AudioEffect( 441 AudioEffect.EFFECT_TYPE_NULL, 442 UUID.fromString(BUNDLE_VOLUME_EFFECT_UUID), 443 0, 444 mp.getAudioSessionId()); 445 vc.setEnabled(true); 446 447 AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 448 assertNotNull("null AudioManager", am); 449 int originalVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC); 450 am.setStreamVolume(AudioManager.STREAM_MUSIC, 451 am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0); 452 getVisualizer(mp.getAudioSessionId()); 453 mp.start(); 454 455 mVisualizer.setEnabled(true); 456 assertTrue("visualizer not enabled", mVisualizer.getEnabled()); 457 Thread.sleep(100); 458 int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS); 459 assertEquals("setMeasurementMode() for PEAK_RMS doesn't report success", 460 Visualizer.SUCCESS, status); 461 // make sure we're playing long enough so the measurement is valid 462 int currentPosition = mp.getCurrentPosition(); 463 final int maxTry = 100; 464 int tryCount = 0; 465 while (currentPosition < 400 && tryCount < maxTry) { 466 Thread.sleep(50); 467 currentPosition = mp.getCurrentPosition(); 468 tryCount++; 469 } 470 assertTrue("MediaPlayer not ready", tryCount < maxTry); 471 472 MeasurementPeakRms measurement = new MeasurementPeakRms(); 473 status = mVisualizer.getMeasurementPeakRms(measurement); 474 mp.stop(); 475 mp.release(); 476 am.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0); 477 assertEquals("getMeasurementPeakRms() reports failure", 478 Visualizer.SUCCESS, status); 479 Log.i("VisTest", "peak="+measurement.mPeak+" rms="+measurement.mRms); 480 int deltaPeak = Math.abs(measurement.mPeak - EXPECTED_PEAK_MB); 481 int deltaRms = Math.abs(measurement.mRms - EXPECTED_RMS_MB); 482 assertTrue("peak deviation in mB=" + deltaPeak, deltaPeak < MAX_MEASUREMENT_ERROR_MB); 483 assertTrue("RMS deviation in mB=" + deltaRms, deltaRms < MAX_MEASUREMENT_ERROR_MB); 484 485 } catch (IllegalStateException e) { 486 fail("method called in wrong state"); 487 } catch (InterruptedException e) { 488 fail("sleep() interrupted"); 489 } finally { 490 if (vc != null) 491 vc.release(); 492 releaseVisualizer(); 493 } 494 } 495 496 //----------------------------------------------------------------- 497 // private methods 498 //---------------------------------- 499 computeEnergy(byte[] data, boolean pcm)500 private int computeEnergy(byte[] data, boolean pcm) { 501 int energy = 0; 502 if (data.length != 0) { 503 if (pcm) { 504 for (int i = 0; i < data.length; i++) { 505 int tmp = ((int)data[i] & 0xFF) - 128; 506 energy += tmp*tmp; 507 } 508 } else { 509 // Note that data[0] is real part of FFT at DC 510 // and data[1] is real part of FFT at Nyquist, 511 // but for the purposes of energy calculation we 512 // don't need to treat them specially. 513 for (int i = 0; i < data.length; i += 2) { 514 int real = (int)data[i]; 515 int img = (int)data[i + 1]; 516 energy += real * real + img * img; 517 } 518 } 519 } 520 return energy; 521 } 522 getVisualizer(int session)523 private void getVisualizer(int session) { 524 if (mVisualizer == null || session != mSession) { 525 if (session != mSession && mVisualizer != null) { 526 mVisualizer.release(); 527 mVisualizer = null; 528 } 529 try { 530 mVisualizer = new Visualizer(session); 531 mSession = session; 532 } catch (IllegalArgumentException e) { 533 Log.e(TAG, "getVisualizer() Visualizer not found exception: "+e); 534 } catch (UnsupportedOperationException e) { 535 Log.e(TAG, "getVisualizer() Effect library not loaded exception: "+e); 536 } 537 } 538 assertNotNull("could not create mVisualizer", mVisualizer); 539 } 540 releaseVisualizer()541 private void releaseVisualizer() { 542 if (mVisualizer != null) { 543 mVisualizer.release(); 544 mVisualizer = null; 545 } 546 } 547 waitForLooperInitialization_l()548 private void waitForLooperInitialization_l() { 549 int looperWaitCount = MAX_LOOPER_WAIT_COUNT; 550 while (!mInitialized && (looperWaitCount-- > 0)) { 551 try { 552 mLock.wait(); 553 } catch(Exception e) { 554 } 555 } 556 assertTrue(mInitialized); 557 } 558 createListenerLooper()559 private void createListenerLooper() { 560 mListenerThread = new Thread() { 561 @Override 562 public void run() { 563 // Set up a looper to be used by mEffect. 564 Looper.prepare(); 565 566 // Save the looper so that we can terminate this thread 567 // after we are done with it. 568 mLooper = Looper.myLooper(); 569 570 synchronized(mLock) { 571 if (mVisualizer != null) { 572 mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() { 573 public void onWaveFormDataCapture( 574 Visualizer visualizer, byte[] waveform, int samplingRate) { 575 synchronized(mLock) { 576 if (visualizer == mVisualizer) { 577 if (mCaptureWaveform) { 578 mWaveform = waveform; 579 mLock.notify(); 580 } 581 } 582 } 583 } 584 585 public void onFftDataCapture( 586 Visualizer visualizer, byte[] fft, int samplingRate) { 587 synchronized(mLock) { 588 Log.e(TAG, "onFftDataCapture 2 mCaptureFft: "+mCaptureFft); 589 if (visualizer == mVisualizer) { 590 if (mCaptureFft) { 591 mFft = fft; 592 mLock.notify(); 593 } 594 } 595 } 596 } 597 }, 598 10000, 599 true, 600 true); 601 } 602 mInitialized = true; 603 mLock.notify(); 604 } 605 Looper.loop(); // Blocks forever until Looper.quit() is called. 606 } 607 }; 608 mListenerThread.start(); 609 } 610 /* 611 * Terminates the listener looper thread. 612 */ terminateListenerLooper()613 private void terminateListenerLooper() { 614 if (mListenerThread != null) { 615 if (mLooper != null) { 616 mLooper.quit(); 617 mLooper = null; 618 } 619 try { 620 mListenerThread.join(); 621 } catch(InterruptedException e) { 622 } 623 mListenerThread = null; 624 } 625 } 626 } 627