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 com.android.mediaframeworktest.functional.audio; 18 19 import com.android.mediaframeworktest.MediaFrameworkTest; 20 import com.android.mediaframeworktest.MediaNames; 21 import android.content.Context; 22 import android.content.res.AssetFileDescriptor; 23 import android.media.audiofx.AudioEffect; 24 import android.media.AudioManager; 25 import android.media.audiofx.Visualizer; 26 import android.media.MediaPlayer; 27 28 import android.os.Looper; 29 import android.test.suitebuilder.annotation.LargeTest; 30 import android.test.suitebuilder.annotation.MediumTest; 31 import android.test.suitebuilder.annotation.Suppress; 32 import android.test.ActivityInstrumentationTestCase2; 33 import android.util.Log; 34 35 import java.nio.ByteOrder; 36 import java.nio.ByteBuffer; 37 import java.util.UUID; 38 39 /** 40 * Junit / Instrumentation test case for the media AudioTrack api 41 42 */ 43 public class MediaVisualizerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { 44 private String TAG = "MediaVisualizerTest"; 45 private final static int MIN_CAPTURE_RATE_MAX = 20000; 46 private final static int MIN_SAMPLING_RATE = 8000000; 47 private final static int MAX_SAMPLING_RATE = 48000000; 48 private final static int MIN_CAPTURE_SIZE_MAX = 1024; 49 private final static int MAX_CAPTURE_SIZE_MIN = 128; 50 // Implementor UUID for volume controller effect defined in 51 // frameworks/base/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp 52 private final static UUID VOLUME_EFFECT_UUID = 53 UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"); 54 55 private Visualizer mVisualizer = null; 56 private int mSession = -1; 57 private boolean mInitialized = false; 58 private Looper mLooper = null; 59 private final Object lock = new Object(); 60 private byte[] mWaveform = null; 61 private byte[] mFft = null; 62 private boolean mCaptureWaveform = false; 63 private boolean mCaptureFft = false; 64 65 public MediaVisualizerTest() { 66 super("com.android.mediaframeworktest", MediaFrameworkTest.class); 67 } 68 69 @Override 70 protected void setUp() throws Exception { 71 super.setUp(); 72 } 73 74 @Override 75 protected void tearDown() throws Exception { 76 super.tearDown(); 77 releaseVisualizer(); 78 } 79 80 private static void assumeTrue(String message, boolean cond) { 81 assertTrue("(assume)"+message, cond); 82 } 83 84 private void log(String testName, String message) { 85 Log.v(TAG, "["+testName+"] "+message); 86 } 87 88 private void loge(String testName, String message) { 89 Log.e(TAG, "["+testName+"] "+message); 90 } 91 92 //----------------------------------------------------------------- 93 // VISUALIZER TESTS: 94 //---------------------------------- 95 96 97 //----------------------------------------------------------------- 98 // 0 - constructor 99 //---------------------------------- 100 101 //Test case 0.0: test constructor and release 102 @LargeTest 103 public void test0_0ConstructorAndRelease() throws Exception { 104 boolean result = false; 105 String msg = "test1_0ConstructorAndRelease()"; 106 Visualizer visualizer = null; 107 try { 108 visualizer = new Visualizer(0); 109 assertNotNull(msg + ": could not create Visualizer", visualizer); 110 result = true; 111 } catch (IllegalArgumentException e) { 112 msg = msg.concat(": Visualizer not found"); 113 } catch (UnsupportedOperationException e) { 114 msg = msg.concat(": Effect library not loaded"); 115 } finally { 116 if (visualizer != null) { 117 visualizer.release(); 118 } 119 } 120 assertTrue(msg, result); 121 } 122 123 124 //----------------------------------------------------------------- 125 // 1 - get/set parameters 126 //---------------------------------- 127 128 //Test case 1.0: check capture rate and sampling rate 129 @LargeTest 130 public void test1_0CaptureRates() throws Exception { 131 boolean result = false; 132 String msg = "test1_0CaptureRates()"; 133 getVisualizer(0); 134 try { 135 int captureRate = mVisualizer.getMaxCaptureRate(); 136 assertTrue(msg +": insufficient max capture rate", 137 captureRate >= MIN_CAPTURE_RATE_MAX); 138 int samplingRate = mVisualizer.getSamplingRate(); 139 assertTrue(msg +": invalid sampling rate", 140 samplingRate >= MIN_SAMPLING_RATE && samplingRate <= MAX_SAMPLING_RATE); 141 result = true; 142 } catch (IllegalArgumentException e) { 143 msg = msg.concat(": Bad parameter value"); 144 loge(msg, "Bad parameter value"); 145 } catch (UnsupportedOperationException e) { 146 msg = msg.concat(": get parameter() rejected"); 147 loge(msg, "get parameter() rejected"); 148 } catch (IllegalStateException e) { 149 msg = msg.concat("get parameter() called in wrong state"); 150 loge(msg, "get parameter() called in wrong state"); 151 } finally { 152 releaseVisualizer(); 153 } 154 assertTrue(msg, result); 155 } 156 157 //Test case 1.1: check capture size 158 @LargeTest 159 public void test1_1CaptureSize() throws Exception { 160 boolean result = false; 161 String msg = "test1_1CaptureSize()"; 162 getVisualizer(0); 163 try { 164 int[] range = mVisualizer.getCaptureSizeRange(); 165 assertTrue(msg +": insufficient min capture size", 166 range[0] <= MAX_CAPTURE_SIZE_MIN); 167 assertTrue(msg +": insufficient min capture size", 168 range[1] >= MIN_CAPTURE_SIZE_MAX); 169 mVisualizer.setCaptureSize(range[0]); 170 assertEquals(msg +": insufficient min capture size", 171 range[0], mVisualizer.getCaptureSize()); 172 mVisualizer.setCaptureSize(range[1]); 173 assertEquals(msg +": insufficient min capture size", 174 range[1], mVisualizer.getCaptureSize()); 175 result = true; 176 } catch (IllegalArgumentException e) { 177 msg = msg.concat(": Bad parameter value"); 178 loge(msg, "Bad parameter value"); 179 } catch (UnsupportedOperationException e) { 180 msg = msg.concat(": get parameter() rejected"); 181 loge(msg, "get parameter() rejected"); 182 } catch (IllegalStateException e) { 183 msg = msg.concat("get parameter() called in wrong state"); 184 loge(msg, "get parameter() called in wrong state"); 185 } finally { 186 releaseVisualizer(); 187 } 188 assertTrue(msg, result); 189 } 190 191 //Test case 1.2: check scaling mode 192 @LargeTest 193 public void test1_2ScalingMode() throws Exception { 194 boolean result = false; 195 String msg = "test1_2ScalingMode()"; 196 getVisualizer(0); 197 try { 198 int res = mVisualizer.setScalingMode(Visualizer.SCALING_MODE_AS_PLAYED); 199 assertEquals(msg + ": setting SCALING_MODE_AS_PLAYED failed", 200 res, Visualizer.SUCCESS); 201 int mode = mVisualizer.getScalingMode(); 202 assertEquals(msg + ": setting SCALING_MODE_AS_PLAYED didn't stick", 203 mode, Visualizer.SCALING_MODE_AS_PLAYED); 204 205 res = mVisualizer.setScalingMode(Visualizer.SCALING_MODE_NORMALIZED); 206 assertEquals(msg + ": setting SCALING_MODE_NORMALIZED failed", 207 res, Visualizer.SUCCESS); 208 mode = mVisualizer.getScalingMode(); 209 assertEquals(msg + ": setting SCALING_MODE_NORMALIZED didn't stick", 210 mode, Visualizer.SCALING_MODE_NORMALIZED); 211 212 result = true; 213 } catch (IllegalStateException e) { 214 msg = msg.concat("IllegalStateException"); 215 loge(msg, "set/get parameter() called in wrong state: " + e); 216 } finally { 217 releaseVisualizer(); 218 } 219 assertTrue(msg, result); 220 } 221 222 //----------------------------------------------------------------- 223 // 2 - check capture 224 //---------------------------------- 225 226 //Test case 2.0: test capture in polling mode 227 @LargeTest 228 public void test2_0PollingCapture() throws Exception { 229 boolean result = false; 230 String msg = "test2_0PollingCapture()"; 231 AudioEffect vc = null; 232 MediaPlayer mp = null; 233 AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); 234 int ringerMode = am.getRingerMode(); 235 am.setRingerMode(AudioManager.RINGER_MODE_NORMAL); 236 int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 237 am.setStreamVolume(AudioManager.STREAM_MUSIC, 238 am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 239 0); 240 241 try { 242 // creating a volume controller on output mix ensures that ro.audio.silent mutes 243 // audio after the effects and not before 244 vc = new AudioEffect( 245 AudioEffect.EFFECT_TYPE_NULL, 246 VOLUME_EFFECT_UUID, 247 0, 248 0); 249 vc.setEnabled(true); 250 251 mp = new MediaPlayer(); 252 mp.setDataSource(MediaNames.SINE_200_1000); 253 mp.setAudioStreamType(AudioManager.STREAM_MUSIC); 254 getVisualizer(mp.getAudioSessionId()); 255 mVisualizer.setEnabled(true); 256 // check capture on silence 257 byte[] data = new byte[mVisualizer.getCaptureSize()]; 258 mVisualizer.getWaveForm(data); 259 int energy = computeEnergy(data, true); 260 assertEquals(msg +": getWaveForm reports energy for silence", 261 0, energy); 262 mVisualizer.getFft(data); 263 energy = computeEnergy(data, false); 264 assertEquals(msg +": getFft reports energy for silence", 265 0, energy); 266 mp.prepare(); 267 mp.start(); 268 Thread.sleep(500); 269 // check capture on sound 270 mVisualizer.getWaveForm(data); 271 energy = computeEnergy(data, true); 272 assertTrue(msg +": getWaveForm reads insufficient level", 273 energy > 0); 274 mVisualizer.getFft(data); 275 energy = computeEnergy(data, false); 276 assertTrue(msg +": getFft reads insufficient level", 277 energy > 0); 278 result = true; 279 } catch (IllegalArgumentException e) { 280 msg = msg.concat(": Bad parameter value"); 281 loge(msg, "Bad parameter value"); 282 } catch (UnsupportedOperationException e) { 283 msg = msg.concat(": get parameter() rejected"); 284 loge(msg, "get parameter() rejected"); 285 } catch (IllegalStateException e) { 286 msg = msg.concat("get parameter() called in wrong state"); 287 loge(msg, "get parameter() called in wrong state"); 288 } catch (InterruptedException e) { 289 loge(msg, "sleep() interrupted"); 290 } 291 finally { 292 releaseVisualizer(); 293 if (mp != null) { 294 mp.release(); 295 } 296 if (vc != null) { 297 vc.release(); 298 } 299 am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); 300 am.setRingerMode(ringerMode); 301 } 302 assertTrue(msg, result); 303 } 304 305 //Test case 2.1: test capture with listener 306 @LargeTest 307 public void test2_1ListenerCapture() throws Exception { 308 boolean result = false; 309 String msg = "test2_1ListenerCapture()"; 310 AudioEffect vc = null; 311 MediaPlayer mp = null; 312 AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); 313 int ringerMode = am.getRingerMode(); 314 am.setRingerMode(AudioManager.RINGER_MODE_NORMAL); 315 int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 316 am.setStreamVolume(AudioManager.STREAM_MUSIC, 317 am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 318 0); 319 320 try { 321 // creating a volume controller on output mix ensures that ro.audio.silent mutes 322 // audio after the effects and not before 323 vc = new AudioEffect( 324 AudioEffect.EFFECT_TYPE_NULL, 325 VOLUME_EFFECT_UUID, 326 0, 327 0); 328 vc.setEnabled(true); 329 330 mp = new MediaPlayer(); 331 mp.setDataSource(MediaNames.SINE_200_1000); 332 mp.setAudioStreamType(AudioManager.STREAM_MUSIC); 333 334 getVisualizer(mp.getAudioSessionId()); 335 createListenerLooper(); 336 synchronized(lock) { 337 try { 338 lock.wait(1000); 339 } catch(Exception e) { 340 Log.e(TAG, "Looper creation: wait was interrupted."); 341 } 342 } 343 assertTrue(mInitialized); 344 345 mVisualizer.setEnabled(true); 346 347 // check capture on silence 348 synchronized(lock) { 349 try { 350 mCaptureWaveform = true; 351 lock.wait(1000); 352 mCaptureWaveform = false; 353 } catch(Exception e) { 354 Log.e(TAG, "Capture waveform: wait was interrupted."); 355 } 356 } 357 assertNotNull(msg +": waveform capture failed", mWaveform); 358 int energy = computeEnergy(mWaveform, true); 359 assertEquals(msg +": getWaveForm reports energy for silence", 360 0, energy); 361 362 synchronized(lock) { 363 try { 364 mCaptureFft = true; 365 lock.wait(1000); 366 mCaptureFft = false; 367 } catch(Exception e) { 368 Log.e(TAG, "Capture FFT: wait was interrupted."); 369 } 370 } 371 assertNotNull(msg +": FFT capture failed", mFft); 372 energy = computeEnergy(mFft, false); 373 assertEquals(msg +": getFft reports energy for silence", 374 0, energy); 375 376 mp.prepare(); 377 mp.start(); 378 Thread.sleep(500); 379 380 // check capture on sound 381 synchronized(lock) { 382 try { 383 mCaptureWaveform = true; 384 lock.wait(1000); 385 mCaptureWaveform = false; 386 } catch(Exception e) { 387 Log.e(TAG, "Capture waveform: wait was interrupted."); 388 } 389 } 390 assertNotNull(msg +": waveform capture failed", mWaveform); 391 energy = computeEnergy(mWaveform, true); 392 assertTrue(msg +": getWaveForm reads insufficient level", 393 energy > 0); 394 395 synchronized(lock) { 396 try { 397 mCaptureFft = true; 398 lock.wait(1000); 399 mCaptureFft = false; 400 } catch(Exception e) { 401 Log.e(TAG, "Capture FFT: wait was interrupted."); 402 } 403 } 404 assertNotNull(msg +": FFT capture failed", mFft); 405 energy = computeEnergy(mFft, false); 406 assertTrue(msg +": getFft reads insufficient level", 407 energy > 0); 408 409 result = true; 410 } catch (IllegalArgumentException e) { 411 msg = msg.concat(": Bad parameter value"); 412 loge(msg, "Bad parameter value"); 413 } catch (UnsupportedOperationException e) { 414 msg = msg.concat(": get parameter() rejected"); 415 loge(msg, "get parameter() rejected"); 416 } catch (IllegalStateException e) { 417 msg = msg.concat("get parameter() called in wrong state"); 418 loge(msg, "get parameter() called in wrong state"); 419 } catch (InterruptedException e) { 420 loge(msg, "sleep() interrupted"); 421 } 422 finally { 423 terminateListenerLooper(); 424 releaseVisualizer(); 425 if (mp != null) { 426 mp.release(); 427 } 428 if (vc != null) { 429 vc.release(); 430 } 431 am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); 432 am.setRingerMode(ringerMode); 433 } 434 assertTrue(msg, result); 435 } 436 437 //Test case 2.2: test capture in polling mode with volume scaling 438 @LargeTest 439 public void test2_2PollingCaptureVolumeScaling() throws Exception { 440 // test that when playing a sound, the energy measured with Visualizer in 441 // SCALING_MODE_AS_PLAYED mode decreases when lowering the volume 442 boolean result = false; 443 String msg = "test2_2PollingCaptureVolumeScaling()"; 444 AudioEffect vc = null; 445 MediaPlayer mp = null; 446 AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); 447 int ringerMode = am.getRingerMode(); 448 am.setRingerMode(AudioManager.RINGER_MODE_NORMAL); 449 final int volMax = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 450 am.setStreamVolume(AudioManager.STREAM_MUSIC, volMax, 0); 451 452 try { 453 // test setup not related to tested functionality: 454 // creating a volume controller on output mix ensures that ro.audio.silent mutes 455 // audio after the effects and not before 456 vc = new AudioEffect( 457 AudioEffect.EFFECT_TYPE_NULL, 458 VOLUME_EFFECT_UUID, 459 0, 460 0); 461 vc.setEnabled(true); 462 463 mp = new MediaPlayer(); 464 mp.setDataSource(MediaNames.SINE_200_1000); 465 mp.setAudioStreamType(AudioManager.STREAM_MUSIC); 466 getVisualizer(mp.getAudioSessionId()); 467 468 // verify we successfully set the Visualizer in SCALING_MODE_AS_PLAYED mode 469 mVisualizer.setScalingMode(Visualizer.SCALING_MODE_AS_PLAYED); 470 assertTrue(msg + " get volume scaling doesn't return SCALING_MODE_AS_PLAYED", 471 mVisualizer.getScalingMode() == Visualizer.SCALING_MODE_AS_PLAYED); 472 mVisualizer.setEnabled(true); 473 mp.prepare(); 474 mp.start(); 475 Thread.sleep(500); 476 477 // check capture on sound with music volume at max 478 byte[] data = new byte[mVisualizer.getCaptureSize()]; 479 mVisualizer.getWaveForm(data); 480 int energyAtVolMax = computeEnergy(data, true); 481 assertTrue(msg +": getWaveForm reads insufficient level", 482 energyAtVolMax > 0); 483 log(msg, " engergy at max volume = "+energyAtVolMax); 484 485 // check capture on sound with music volume lowered from max 486 am.setStreamVolume(AudioManager.STREAM_MUSIC, (volMax * 2) / 3, 0); 487 Thread.sleep(500); 488 mVisualizer.getWaveForm(data); 489 int energyAtLowerVol = computeEnergy(data, true); 490 assertTrue(msg +": getWaveForm at lower volume reads insufficient level", 491 energyAtLowerVol > 0); 492 log(msg, "energy at lower volume = "+energyAtLowerVol); 493 assertTrue(msg +": getWaveForm didn't report lower energy when volume decreases", 494 energyAtVolMax > energyAtLowerVol); 495 496 result = true; 497 } catch (IllegalArgumentException e) { 498 msg = msg.concat(": IllegalArgumentException"); 499 loge(msg, " hit exception " + e); 500 } catch (UnsupportedOperationException e) { 501 msg = msg.concat(": UnsupportedOperationException"); 502 loge(msg, " hit exception " + e); 503 } catch (IllegalStateException e) { 504 msg = msg.concat("IllegalStateException"); 505 loge(msg, " hit exception " + e); 506 } catch (InterruptedException e) { 507 loge(msg, " sleep() interrupted"); 508 } 509 finally { 510 releaseVisualizer(); 511 if (mp != null) { 512 mp.release(); 513 } 514 if (vc != null) { 515 vc.release(); 516 } 517 am.setRingerMode(ringerMode); 518 } 519 assertTrue(msg, result); 520 } 521 522 //----------------------------------------------------------------- 523 // private methods 524 //---------------------------------- 525 526 private int computeEnergy(byte[] data, boolean unsigned) { 527 int energy = 0; 528 if (data.length != 0) { 529 for (int i = 0; i < data.length; i++) { 530 int tmp; 531 // convert from unsigned 8 bit to signed 16 bit 532 if (unsigned) { 533 tmp = ((int)data[i] & 0xFF) - 128; 534 } else { 535 tmp = (int)data[i]; 536 } 537 energy += tmp*tmp; 538 } 539 energy /= data.length; 540 } 541 return energy; 542 } 543 544 private void getVisualizer(int session) { 545 if (mVisualizer == null || session != mSession) { 546 if (session != mSession && mVisualizer != null) { 547 mVisualizer.release(); 548 mVisualizer = null; 549 } 550 try { 551 mVisualizer = new Visualizer(session); 552 mSession = session; 553 } catch (IllegalArgumentException e) { 554 Log.e(TAG, "getVisualizer() Visualizer not found exception: "+e); 555 } catch (UnsupportedOperationException e) { 556 Log.e(TAG, "getVisualizer() Effect library not loaded exception: "+e); 557 } 558 } 559 assertNotNull("could not create mVisualizer", mVisualizer); 560 } 561 562 private void releaseVisualizer() { 563 if (mVisualizer != null) { 564 mVisualizer.release(); 565 mVisualizer = null; 566 } 567 } 568 569 private void createListenerLooper() { 570 571 new Thread() { 572 @Override 573 public void run() { 574 // Set up a looper to be used by mEffect. 575 Looper.prepare(); 576 577 // Save the looper so that we can terminate this thread 578 // after we are done with it. 579 mLooper = Looper.myLooper(); 580 581 if (mVisualizer != null) { 582 mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() { 583 public void onWaveFormDataCapture( 584 Visualizer visualizer, byte[] waveform, int samplingRate) { 585 synchronized(lock) { 586 if (visualizer == mVisualizer) { 587 if (mCaptureWaveform) { 588 mWaveform = waveform; 589 lock.notify(); 590 } 591 } 592 } 593 } 594 595 public void onFftDataCapture( 596 Visualizer visualizer, byte[] fft, int samplingRate) { 597 synchronized(lock) { 598 if (visualizer == mVisualizer) { 599 if (mCaptureFft) { 600 mFft = fft; 601 lock.notify(); 602 } 603 } 604 } 605 } 606 }, 607 10000, 608 true, 609 true); 610 } 611 612 synchronized(lock) { 613 mInitialized = true; 614 lock.notify(); 615 } 616 Looper.loop(); // Blocks forever until Looper.quit() is called. 617 } 618 }.start(); 619 } 620 /* 621 * Terminates the listener looper thread. 622 */ 623 private void terminateListenerLooper() { 624 if (mLooper != null) { 625 mLooper.quit(); 626 mLooper = null; 627 } 628 } 629 630 } 631