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