1/*
2 * Copyright (c) 2009, Google Inc.
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.soundpooltest;
18
19import android.app.Activity;
20import android.widget.LinearLayout;
21import android.os.Bundle;
22import android.view.ViewGroup;
23import android.widget.Button;
24import android.view.View;
25import android.view.View.OnClickListener;
26import android.view.KeyEvent;
27import android.media.AudioSystem;
28import android.media.AudioManager;
29import android.media.SoundPool;
30import android.media.SoundPool.OnLoadCompleteListener;
31import android.util.Log;
32import java.util.HashMap;
33import java.lang.Math;
34
35import com.android.soundpooltest.R;
36
37public class SoundPoolTest extends Activity
38{
39    private static final String LOG_TAG = "SoundPoolTest";
40    private static final boolean DEBUG = true;
41    private static final boolean VERBOSE = false;
42    private TestThread mThread;
43
44    private static final int[] mTestFiles = new int[] {
45        R.raw.organ441,
46        R.raw.sine441,
47        R.raw.test1,
48        R.raw.test2,
49        R.raw.test3,
50        R.raw.test4,
51        R.raw.test5
52    };
53
54    private final static float SEMITONE = 1.059463094f;
55    private final static float DEFAULT_VOLUME = 0.707f;
56    private final static float MAX_VOLUME = 1.0f;
57    private final static float MIN_VOLUME = 0.01f;
58    private final static int LOW_PRIORITY = 1000;
59    private final static int NORMAL_PRIORITY = 2000;
60    private final static int HIGH_PRIORITY = 3000;
61    private final static int DEFAULT_LOOP = -1;
62    private final static int DEFAULT_SRC_QUALITY = 0;
63    private final static double PI_OVER_2 = Math.PI / 2.0;
64
65    public SoundPoolTest() {}
66
67    private final class TestThread extends java.lang.Thread {
68        private boolean mRunning;
69        private SoundPool mSoundPool = null;
70        private int mLastSample;
71        private int mMaxStreams;
72        private int mLoadStatus;
73        private int[] mSounds;
74        private float mScale[];
75
76        TestThread() {
77            super("SoundPool.TestThread");
78        }
79
80        private final class LoadCompleteCallback implements
81            android.media.SoundPool.OnLoadCompleteListener {
82            public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
83                synchronized(mSoundPool) {
84                    if (DEBUG) Log.d(LOG_TAG, "Sample " + sampleId + " load status = " + status);
85                    if (status != 0) {
86                        mLoadStatus = status;
87                    }
88                    if (sampleId == mLastSample) {
89                        mSoundPool.notify();
90                    }
91                }
92            }
93        }
94
95        private int loadSound(int resId, int priority) {
96            int id = mSoundPool.load(getApplicationContext(), resId, priority);
97            if (id == 0) {
98                Log.e(LOG_TAG, "Unable to open resource");
99            }
100            return id;
101        }
102
103        private int initSoundPool(int numStreams) throws java.lang.InterruptedException {
104
105            if (mSoundPool != null) {
106                if ((mMaxStreams == numStreams) && (mLoadStatus == 0)) return mLoadStatus;
107                mSoundPool.release();
108                mSoundPool = null;
109            }
110
111            // create sound pool
112            mLoadStatus = 0;
113            mMaxStreams = numStreams;
114            mSoundPool = new SoundPool(numStreams, AudioSystem.STREAM_MUSIC, 0);
115            mSoundPool.setOnLoadCompleteListener(new LoadCompleteCallback());
116            int numSounds = mTestFiles.length;
117            mSounds = new int[numSounds];
118
119            // load sounds
120            synchronized(mSoundPool) {
121                for (int index = 0; index < numSounds; index++) {
122                    mSounds[index] = loadSound(mTestFiles[index], NORMAL_PRIORITY);
123                    mLastSample = mSounds[index];
124                }
125                mSoundPool.wait();
126            }
127            return mLoadStatus;
128        }
129
130        private boolean TestSounds() throws java.lang.InterruptedException {
131            if (DEBUG) Log.d(LOG_TAG, "Begin sounds test");
132            int count = mSounds.length;
133            for (int index = 0; index < count; index++) {
134                int id = mSoundPool.play(mSounds[index], DEFAULT_VOLUME, DEFAULT_VOLUME,
135                        NORMAL_PRIORITY, DEFAULT_LOOP, 1.0f);
136                if (DEBUG) Log.d(LOG_TAG, "Start note " + id);
137                if (id == 0) {
138                    Log.e(LOG_TAG, "Error occurred starting note");
139                    return false;
140                }
141                sleep(450);
142                mSoundPool.stop(id);
143                if (DEBUG) Log.d(LOG_TAG, "Stop note " + id);
144                sleep(50);
145            }
146            if (DEBUG) Log.d(LOG_TAG, "End sounds test");
147            return true;
148        }
149
150        private boolean TestScales() throws java.lang.InterruptedException {
151            if (DEBUG) Log.d(LOG_TAG, "Begin scale test");
152
153            // interate through pitch table
154            int count = mScale.length;
155            for (int step = 0; step < count; step++) {
156                int id = mSoundPool.play(mSounds[0], DEFAULT_VOLUME, DEFAULT_VOLUME,
157                        NORMAL_PRIORITY, DEFAULT_LOOP, mScale[step]);
158                if (DEBUG) Log.d(LOG_TAG, "Start note " + id);
159                if (id == 0) {
160                    Log.e(LOG_TAG, "Error occurred starting note");
161                    return false;
162                }
163                sleep(450);
164                mSoundPool.stop(id);
165                if (DEBUG) Log.d(LOG_TAG, "Stop note " + id);
166                sleep(50);
167            }
168            if (DEBUG) Log.d(LOG_TAG, "End scale test");
169            return true;
170        }
171
172        private boolean TestRates() throws java.lang.InterruptedException {
173            if (DEBUG) Log.d(LOG_TAG, "Begin rate test");
174
175            // start the note
176            int count = mScale.length;
177            int id = mSoundPool.play(mSounds[0], DEFAULT_VOLUME, DEFAULT_VOLUME,
178                    NORMAL_PRIORITY, DEFAULT_LOOP, mScale[0]);
179            if (DEBUG) Log.d(LOG_TAG, "Start note " + id);
180            if (id == 0) {
181                Log.e(LOG_TAG, "Test failed - exiting");
182                return false;
183            }
184
185            // modify the pitch
186            for (int step = 1; step < count; step++) {
187                sleep(250);
188                mSoundPool.setRate(id, mScale[step]);
189                if (DEBUG) Log.d(LOG_TAG, "Change rate " + mScale[step]);
190            }
191            mSoundPool.stop(id);
192            if (DEBUG) Log.d(LOG_TAG, "Stop note " + id);
193            if (DEBUG) Log.d(LOG_TAG, "End rate test");
194            return true;
195        }
196
197        private boolean TestPriority() throws java.lang.InterruptedException {
198            if (DEBUG) Log.d(LOG_TAG, "Begin priority test");
199            boolean result = true;
200
201            // play a normal priority looping sound
202            int normalId = mSoundPool.play(mSounds[0], DEFAULT_VOLUME, DEFAULT_VOLUME,
203                    NORMAL_PRIORITY, DEFAULT_LOOP, 1.0f);
204            if (DEBUG) Log.d(LOG_TAG, "Start note " + normalId);
205            if (normalId == 0) {
206                Log.e(LOG_TAG, "Error occurred starting note");
207                return false;
208            }
209            sleep(1000);
210
211            // play a low priority sound
212            int id = mSoundPool.play(mSounds[1], DEFAULT_VOLUME, DEFAULT_VOLUME,
213                    LOW_PRIORITY, DEFAULT_LOOP, 1.0f);
214            if (id != 0) {
215                Log.e(LOG_TAG, "Normal > Low priority test failed");
216                result = false;
217                mSoundPool.stop(id);
218            } else {
219                sleep(1000);
220                Log.i(LOG_TAG, "Normal > Low priority test passed");
221            }
222
223            // play a high priority sound
224            id = mSoundPool.play(mSounds[2], DEFAULT_VOLUME, DEFAULT_VOLUME,
225                    HIGH_PRIORITY, DEFAULT_LOOP, 1.0f);
226            if (id == 0) {
227                Log.e(LOG_TAG, "High > Normal priority test failed");
228                result = false;
229            } else {
230                sleep(1000);
231                Log.i(LOG_TAG, "Stopping high priority");
232                mSoundPool.stop(id);
233                sleep(1000);
234                Log.i(LOG_TAG, "High > Normal priority test passed");
235            }
236
237            // stop normal note
238            Log.i(LOG_TAG, "Stopping normal priority");
239            mSoundPool.stop(normalId);
240            sleep(1000);
241
242            if (DEBUG) Log.d(LOG_TAG, "End priority test");
243            return result;
244        }
245
246        private boolean TestPauseResume() throws java.lang.InterruptedException {
247            if (DEBUG) Log.d(LOG_TAG, "Begin pause/resume test");
248            boolean result = true;
249
250            // play a normal priority looping sound
251            int id = mSoundPool.play(mSounds[0], DEFAULT_VOLUME, DEFAULT_VOLUME,
252                    NORMAL_PRIORITY, DEFAULT_LOOP, 1.0f);
253            if (DEBUG) Log.d(LOG_TAG, "Start note " + id);
254            if (id == 0) {
255                Log.e(LOG_TAG, "Error occurred starting note");
256                return false;
257            }
258            sleep(2500);
259
260            // pause and resume sound a few times
261            for (int count = 0; count < 5; count++) {
262                if (DEBUG) Log.d(LOG_TAG, "Pause note " + id);
263                mSoundPool.pause(id);
264                sleep(1000);
265                if (DEBUG) Log.d(LOG_TAG, "Resume note " + id);
266                mSoundPool.resume(id);
267                sleep(1000);
268            }
269
270            if (DEBUG) Log.d(LOG_TAG, "Stop note " + id);
271            mSoundPool.stop(id);
272            sleep(1000);
273
274            // play 5 sounds, forces one to be stolen
275            int ids[] = new int[5];
276            for (int i = 0; i < 5; i++) {
277                ids[i] = mSoundPool.play(mSounds[0], DEFAULT_VOLUME, DEFAULT_VOLUME,
278                        NORMAL_PRIORITY, DEFAULT_LOOP, mScale[i]);
279                if (DEBUG) Log.d(LOG_TAG, "Start note " + ids[i]);
280                if (ids[i] == 0) {
281                    Log.e(LOG_TAG, "Error occurred starting note");
282                    return false;
283                }
284                sleep(1000);
285            }
286
287            // pause and resume sound a few times
288            for (int count = 0; count < 5; count++) {
289                if (DEBUG) Log.d(LOG_TAG, "autoPause");
290                mSoundPool.autoPause();
291                sleep(1000);
292                if (DEBUG) Log.d(LOG_TAG, "autoResume");
293                mSoundPool.autoResume();
294                sleep(1000);
295            }
296
297            for (int i = 0; i < 5; i++) {
298                if (DEBUG) Log.d(LOG_TAG, "Stop note " + ids[i]);
299                mSoundPool.stop(ids[i]);
300            }
301
302            if (DEBUG) Log.d(LOG_TAG, "End pause/resume test");
303            return result;
304        }
305
306        private boolean TestVolume() throws java.lang.InterruptedException {
307            if (DEBUG) Log.d(LOG_TAG, "Begin volume test");
308
309            // start the note
310            int id = mSoundPool.play(mSounds[0], 0.0f, 1.0f, NORMAL_PRIORITY, DEFAULT_LOOP, mScale[0]);
311            if (DEBUG) Log.d(LOG_TAG, "Start note " + id);
312            if (id == 0) {
313                Log.e(LOG_TAG, "Test failed - exiting");
314                return false;
315            }
316
317            // pan from right to left
318            for (int count = 0; count < 101; count++) {
319                sleep(50);
320                double radians = PI_OVER_2 * count / 100.0;
321                float leftVolume = (float) Math.sin(radians);
322                float rightVolume = (float) Math.cos(radians);
323                mSoundPool.setVolume(id, leftVolume, rightVolume);
324                if (DEBUG) Log.d(LOG_TAG, "Change volume (" + leftVolume + "," + rightVolume + ")");
325            }
326
327            mSoundPool.stop(id);
328            if (DEBUG) Log.d(LOG_TAG, "End volume test");
329            return true;
330        }
331
332        public void run() {
333            if (DEBUG) Log.d(LOG_TAG, "Test thread running");
334
335            // initialize
336            mRunning = true;
337            int failures = 0;
338
339            // initialize pitch table
340            float pitch = 0.5f;
341            mScale = new float[13];
342            for (int i = 0; i < 13; ++i) {
343                mScale[i] = pitch;
344                pitch *= SEMITONE;
345            }
346
347            try {
348
349                // do single stream tests
350                initSoundPool(1);
351                if (!TestSounds()) failures = failures + 1;
352                if (!TestScales()) failures = failures + 1;
353                if (!TestRates()) failures = failures + 1;
354                if (!TestPriority()) failures = failures + 1;
355                if (!TestVolume()) failures = failures + 1;
356
357                // do multiple stream tests
358                initSoundPool(4);
359                if (!TestPauseResume()) failures = failures + 1;
360
361            } catch (java.lang.InterruptedException e) {
362                if (DEBUG) Log.d(LOG_TAG, "Test interrupted");
363                failures = failures + 1;
364            } finally {
365                mRunning = false;
366            }
367
368            // release sound pool
369            if (mSoundPool != null) {
370                mSoundPool.release();
371                mSoundPool = null;
372            }
373
374            // output stats
375            if (DEBUG) Log.d(LOG_TAG, "Test thread exit");
376            if (failures == 0) {
377                Log.i(LOG_TAG, "All tests passed");
378            } else {
379                Log.i(LOG_TAG, failures + " tests failed");
380            }
381        }
382
383        public void quit() {
384            if (DEBUG) Log.d(LOG_TAG, "interrupt");
385            interrupt();
386            while (mRunning) {
387                try {
388                    sleep(20);
389                } catch (java.lang.InterruptedException e) { }
390            }
391            if (DEBUG) Log.d(LOG_TAG, "quit");
392        }
393    }
394
395    private void startTests() {
396        mThread = new TestThread();
397        mThread.start();
398    }
399
400    protected void onPause()
401    {
402        Log.v(LOG_TAG, "onPause");
403        super.onPause();
404        mThread.quit();
405        mThread = null;
406    }
407
408    protected void onResume()
409    {
410        Log.v(LOG_TAG, "onResume");
411        super.onResume();
412        startTests();
413    }
414
415    public void onCreate(Bundle icicle)
416    {
417        super.onCreate(icicle);
418        setVolumeControlStream(AudioManager.STREAM_MUSIC);
419    }
420}
421
422