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