MediaVisualizerTest.java revision c81e0ad9bd438575389623f523a28e2e6ca69236
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    //-----------------------------------------------------------------
192    // 2 - check capture
193    //----------------------------------
194
195    //Test case 2.0: test capture in polling mode
196    @LargeTest
197    public void test2_0PollingCapture() throws Exception {
198        boolean result = false;
199        String msg = "test2_0PollingCapture()";
200        AudioEffect vc = null;
201        MediaPlayer mp = null;
202        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
203        int ringerMode = am.getRingerMode();
204        am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
205        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
206        am.setStreamVolume(AudioManager.STREAM_MUSIC,
207                           am.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
208                           0);
209
210        try {
211            // creating a volume controller on output mix ensures that ro.audio.silent mutes
212            // audio after the effects and not before
213            vc = new AudioEffect(
214                                AudioEffect.EFFECT_TYPE_NULL,
215                                VOLUME_EFFECT_UUID,
216                                0,
217                                0);
218            vc.setEnabled(true);
219
220            mp = new MediaPlayer();
221            mp.setDataSource(MediaNames.SINE_200_1000);
222            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
223            getVisualizer(mp.getAudioSessionId());
224            mVisualizer.setEnabled(true);
225            // check capture on silence
226            byte[] data = new byte[mVisualizer.getCaptureSize()];
227            mVisualizer.getWaveForm(data);
228            int energy = computeEnergy(data, true);
229            assertEquals(msg +": getWaveForm reports energy for silence",
230                    0, energy);
231            mVisualizer.getFft(data);
232            energy = computeEnergy(data, false);
233            assertEquals(msg +": getFft reports energy for silence",
234                    0, energy);
235            mp.prepare();
236            mp.start();
237            Thread.sleep(500);
238            // check capture on sound
239            mVisualizer.getWaveForm(data);
240            energy = computeEnergy(data, true);
241            assertTrue(msg +": getWaveForm reads insufficient level",
242                    energy > 0);
243            mVisualizer.getFft(data);
244            energy = computeEnergy(data, false);
245            assertTrue(msg +": getFft reads insufficient level",
246                    energy > 0);
247            result = true;
248        } catch (IllegalArgumentException e) {
249            msg = msg.concat(": Bad parameter value");
250            loge(msg, "Bad parameter value");
251        } catch (UnsupportedOperationException e) {
252            msg = msg.concat(": get parameter() rejected");
253            loge(msg, "get parameter() rejected");
254        } catch (IllegalStateException e) {
255            msg = msg.concat("get parameter() called in wrong state");
256            loge(msg, "get parameter() called in wrong state");
257        } catch (InterruptedException e) {
258            loge(msg, "sleep() interrupted");
259        }
260        finally {
261            releaseVisualizer();
262            if (mp != null) {
263                mp.release();
264            }
265            if (vc != null) {
266                vc.release();
267            }
268            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
269            am.setRingerMode(ringerMode);
270        }
271        assertTrue(msg, result);
272    }
273
274    //Test case 2.1: test capture with listener
275    @LargeTest
276    public void test2_1ListenerCapture() throws Exception {
277        boolean result = false;
278        String msg = "test2_1ListenerCapture()";
279        AudioEffect vc = null;
280        MediaPlayer mp = null;
281        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
282        int ringerMode = am.getRingerMode();
283        am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
284        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
285        am.setStreamVolume(AudioManager.STREAM_MUSIC,
286                           am.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
287                           0);
288
289        try {
290            // creating a volume controller on output mix ensures that ro.audio.silent mutes
291            // audio after the effects and not before
292            vc = new AudioEffect(
293                                AudioEffect.EFFECT_TYPE_NULL,
294                                VOLUME_EFFECT_UUID,
295                                0,
296                                0);
297            vc.setEnabled(true);
298
299            mp = new MediaPlayer();
300            mp.setDataSource(MediaNames.SINE_200_1000);
301            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
302
303            getVisualizer(mp.getAudioSessionId());
304            createListenerLooper();
305            synchronized(lock) {
306                try {
307                    lock.wait(1000);
308                } catch(Exception e) {
309                    Log.e(TAG, "Looper creation: wait was interrupted.");
310                }
311            }
312            assertTrue(mInitialized);
313
314            mVisualizer.setEnabled(true);
315
316            // check capture on silence
317            synchronized(lock) {
318                try {
319                    mCaptureWaveform = true;
320                    lock.wait(1000);
321                    mCaptureWaveform = false;
322                } catch(Exception e) {
323                    Log.e(TAG, "Capture waveform: wait was interrupted.");
324                }
325            }
326            assertNotNull(msg +": waveform capture failed", mWaveform);
327            int energy = computeEnergy(mWaveform, true);
328            assertEquals(msg +": getWaveForm reports energy for silence",
329                    0, energy);
330
331            synchronized(lock) {
332                try {
333                    mCaptureFft = true;
334                    lock.wait(1000);
335                    mCaptureFft = false;
336                } catch(Exception e) {
337                    Log.e(TAG, "Capture FFT: wait was interrupted.");
338                }
339            }
340            assertNotNull(msg +": FFT capture failed", mFft);
341            energy = computeEnergy(mFft, false);
342            assertEquals(msg +": getFft reports energy for silence",
343                    0, energy);
344
345            mp.prepare();
346            mp.start();
347            Thread.sleep(500);
348
349            // check capture on sound
350            synchronized(lock) {
351                try {
352                    mCaptureWaveform = true;
353                    lock.wait(1000);
354                    mCaptureWaveform = false;
355                } catch(Exception e) {
356                    Log.e(TAG, "Capture waveform: wait was interrupted.");
357                }
358            }
359            assertNotNull(msg +": waveform capture failed", mWaveform);
360            energy = computeEnergy(mWaveform, true);
361            assertTrue(msg +": getWaveForm reads insufficient level",
362                    energy > 0);
363
364            synchronized(lock) {
365                try {
366                    mCaptureFft = true;
367                    lock.wait(1000);
368                    mCaptureFft = false;
369                } catch(Exception e) {
370                    Log.e(TAG, "Capture FFT: wait was interrupted.");
371                }
372            }
373            assertNotNull(msg +": FFT capture failed", mFft);
374            energy = computeEnergy(mFft, false);
375            assertTrue(msg +": getFft reads insufficient level",
376                    energy > 0);
377
378            result = true;
379        } catch (IllegalArgumentException e) {
380            msg = msg.concat(": Bad parameter value");
381            loge(msg, "Bad parameter value");
382        } catch (UnsupportedOperationException e) {
383            msg = msg.concat(": get parameter() rejected");
384            loge(msg, "get parameter() rejected");
385        } catch (IllegalStateException e) {
386            msg = msg.concat("get parameter() called in wrong state");
387            loge(msg, "get parameter() called in wrong state");
388        } catch (InterruptedException e) {
389            loge(msg, "sleep() interrupted");
390        }
391        finally {
392            terminateListenerLooper();
393            releaseVisualizer();
394            if (mp != null) {
395                mp.release();
396            }
397            if (vc != null) {
398                vc.release();
399            }
400            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
401            am.setRingerMode(ringerMode);
402        }
403        assertTrue(msg, result);
404    }
405
406    //-----------------------------------------------------------------
407    // private methods
408    //----------------------------------
409
410    private int computeEnergy(byte[] data, boolean unsigned) {
411        int energy = 0;
412        if (data.length != 0) {
413            for (int i = 0; i < data.length; i++) {
414                int tmp;
415                // convert from unsigned 8 bit to signed 16 bit
416                if (unsigned) {
417                    tmp = ((int)data[i] & 0xFF) - 128;
418                } else {
419                    tmp = (int)data[i];
420                }
421                energy += tmp*tmp;
422            }
423            energy /= data.length;
424        }
425        return energy;
426    }
427
428    private void getVisualizer(int session) {
429         if (mVisualizer == null || session != mSession) {
430             if (session != mSession && mVisualizer != null) {
431                 mVisualizer.release();
432                 mVisualizer = null;
433             }
434             try {
435                mVisualizer = new Visualizer(session);
436                mSession = session;
437            } catch (IllegalArgumentException e) {
438                Log.e(TAG, "getVisualizer() Visualizer not found exception: "+e);
439            } catch (UnsupportedOperationException e) {
440                Log.e(TAG, "getVisualizer() Effect library not loaded exception: "+e);
441            }
442         }
443         assertNotNull("could not create mVisualizer", mVisualizer);
444    }
445
446    private void releaseVisualizer() {
447        if (mVisualizer != null) {
448            mVisualizer.release();
449            mVisualizer = null;
450        }
451   }
452
453    private void createListenerLooper() {
454
455        new Thread() {
456            @Override
457            public void run() {
458                // Set up a looper to be used by mEffect.
459                Looper.prepare();
460
461                // Save the looper so that we can terminate this thread
462                // after we are done with it.
463                mLooper = Looper.myLooper();
464
465                if (mVisualizer != null) {
466                    mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
467                        public void onWaveFormDataCapture(
468                                Visualizer visualizer, byte[] waveform, int samplingRate) {
469                            synchronized(lock) {
470                                if (visualizer == mVisualizer) {
471                                    if (mCaptureWaveform) {
472                                        mWaveform = waveform;
473                                        lock.notify();
474                                    }
475                                }
476                            }
477                        }
478
479                        public void onFftDataCapture(
480                                Visualizer visualizer, byte[] fft, int samplingRate) {
481                            synchronized(lock) {
482                                if (visualizer == mVisualizer) {
483                                    if (mCaptureFft) {
484                                        mFft = fft;
485                                        lock.notify();
486                                    }
487                                }
488                            }
489                        }
490                    },
491                    10000,
492                    true,
493                    true);
494                }
495
496                synchronized(lock) {
497                    mInitialized = true;
498                    lock.notify();
499                }
500                Looper.loop();  // Blocks forever until Looper.quit() is called.
501            }
502        }.start();
503    }
504    /*
505     * Terminates the listener looper thread.
506     */
507    private void terminateListenerLooper() {
508        if (mLooper != null) {
509            mLooper.quit();
510            mLooper = null;
511        }
512    }
513
514}
515