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
17package com.android.mediaframeworktest.functional.audio;
18
19import com.android.mediaframeworktest.MediaFrameworkTest;
20import com.android.mediaframeworktest.MediaNames;
21import android.content.Context;
22import android.content.res.AssetFileDescriptor;
23import android.media.audiofx.AudioEffect;
24import android.media.AudioManager;
25import android.media.audiofx.Visualizer;
26import android.media.MediaPlayer;
27
28import android.os.Looper;
29import android.test.suitebuilder.annotation.LargeTest;
30import android.test.suitebuilder.annotation.MediumTest;
31import android.test.suitebuilder.annotation.Suppress;
32import android.test.ActivityInstrumentationTestCase2;
33import android.util.Log;
34
35import java.nio.ByteOrder;
36import java.nio.ByteBuffer;
37import java.util.UUID;
38
39/**
40 * Junit / Instrumentation test case for the media AudioTrack api
41
42 */
43public 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