1/*
2 * Copyright (C) 2015 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 org.drrickorang.loopback;
18
19import android.app.Application;
20import android.content.Context;
21import android.content.pm.PackageManager;
22import android.content.res.Configuration;
23import android.media.AudioFormat;
24import android.media.AudioManager;
25import android.media.AudioRecord;
26import android.media.AudioTrack;
27import android.media.MediaRecorder;
28import android.os.Build;
29import android.util.Log;
30
31
32/**
33 * This class maintain global application states, so it also keeps and computes the default
34 * values of all the audio settings.
35 */
36
37public class LoopbackApplication extends Application {
38    private static final String TAG = "LoopbackApplication";
39
40    // here defines all the initial setting values, some get modified in ComputeDefaults()
41    private int mSamplingRate = 48000;
42    private int mChannelIndex = -1;
43    private int mPlayerBufferSizeInBytes = 0; // for both native and java
44    private int mRecorderBuffSizeInBytes = 0; // for both native and java
45    private int mAudioThreadType = Constant.AUDIO_THREAD_TYPE_JAVA; //0:Java, 1:Native (JNI)
46    private int mMicSource = 3; //maps to MediaRecorder.AudioSource.VOICE_RECOGNITION;
47    private int mPerformanceMode = -1; // DEFAULT
48    private int mIgnoreFirstFrames = 0;
49    private int mBufferTestDurationInSeconds = 5;
50    private int mBufferTestWavePlotDurationInSeconds = 7;
51    private int mNumberOfLoadThreads = 4;
52    private boolean mCaptureSysTraceEnabled = false;
53    private boolean mCaptureBugreportEnabled = false;
54    private boolean mCaptureWavSnippetsEnabled = false;
55    private boolean mSoundLevelCalibrationEnabled = false;
56    private int mNumStateCaptures = Constant.DEFAULT_NUM_CAPTURES;
57
58    public void setDefaults() {
59        if (isSafeToUseSles()) {
60            mAudioThreadType = Constant.AUDIO_THREAD_TYPE_NATIVE;
61        } else {
62            mAudioThreadType = Constant.AUDIO_THREAD_TYPE_JAVA;
63        }
64
65        computeDefaults();
66    }
67
68    int getSamplingRate() {
69        return mSamplingRate;
70    }
71
72    void setSamplingRate(int samplingRate) {
73        mSamplingRate = clamp(samplingRate, Constant.SAMPLING_RATE_MIN, Constant.SAMPLING_RATE_MAX);
74    }
75
76    int getChannelIndex() { return mChannelIndex; }
77
78    void setChannelIndex(int channelIndex) { mChannelIndex = channelIndex; }
79
80    int getAudioThreadType() {
81        return mAudioThreadType;
82    }
83
84
85    void setAudioThreadType(int audioThreadType) {
86        if (isSafeToUseSles() && audioThreadType != Constant.AUDIO_THREAD_TYPE_JAVA) {
87            //safe to use native and Java thread not selected
88            mAudioThreadType = Constant.AUDIO_THREAD_TYPE_NATIVE;
89        } else {
90            mAudioThreadType = Constant.AUDIO_THREAD_TYPE_JAVA;
91        }
92    }
93
94
95    int getMicSource() {
96        return mMicSource;
97    }
98
99
100    int mapMicSource(int threadType, int source) {
101        int mappedSource = 0;
102
103        //experiment with remote submix
104        if (threadType == Constant.AUDIO_THREAD_TYPE_JAVA) {
105            switch (source) {
106            default:
107            case 0: //DEFAULT
108                mappedSource = MediaRecorder.AudioSource.DEFAULT;
109                break;
110            case 1: //MIC
111                mappedSource = MediaRecorder.AudioSource.MIC;
112                break;
113            case 2: //CAMCORDER
114                mappedSource = MediaRecorder.AudioSource.CAMCORDER;
115                break;
116            case 3: //VOICE_RECOGNITION
117                mappedSource = MediaRecorder.AudioSource.VOICE_RECOGNITION;
118                break;
119            case 4: //VOICE_COMMUNICATION
120                mappedSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION;
121                break;
122            case 5: //REMOTE_SUBMIX (JAVA ONLY)
123                mappedSource = MediaRecorder.AudioSource.REMOTE_SUBMIX;
124                break;
125            case 6: //UNPROCESSED
126                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
127                    mappedSource = 9 /*MediaRecorder.AudioSource.UNPROCESSED*/;
128                } else {
129                    mappedSource = MediaRecorder.AudioSource.DEFAULT;
130                }
131                break;
132            }
133        } else if (threadType == Constant.AUDIO_THREAD_TYPE_NATIVE) {
134            // FIXME taken from OpenSLES_AndroidConfiguration.h
135            switch (source) {
136            default:
137            case 0: //DEFAULT
138                mappedSource = 0x00; //SL_ANDROID_RECORDING_PRESET_NONE
139                break;
140            case 1: //MIC
141                mappedSource = 0x01; //SL_ANDROID_RECORDING_PRESET_GENERIC
142                break;
143            case 2: //CAMCORDER
144                mappedSource = 0x02; //SL_ANDROID_RECORDING_PRESET_CAMCORDER
145                break;
146            case 3: //VOICE_RECOGNITION
147                mappedSource = 0x03; //SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
148                break;
149            case 4: //VOICE_COMMUNICATION
150                mappedSource = 0x04; //SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION
151                break;
152            case 5: //REMOTE_SUBMIX (JAVA ONLY)
153                mappedSource = 0x00; //SL_ANDROID_RECORDING_PRESET_NONE;
154                break;
155            case 6: //UNPROCESSED
156                // FIXME should use >=
157                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
158                    mappedSource = 0x05; //SL_ANDROID_RECORDING_PRESET_UNPROCESSED;
159                } else {
160                    mappedSource = 0x00; //SL_ANDROID_RECORDING_PRESET_NONE
161                }
162                break;
163            }
164        }
165
166        return mappedSource;
167    }
168
169
170    String getMicSourceString(int source) {
171        String name = null;
172        String[] myArray = getResources().getStringArray(R.array.mic_source_array);
173
174        if (myArray != null && source >= 0 && source < myArray.length) {
175            name = myArray[source];
176        }
177        return name;
178    }
179
180    void setMicSource(int micSource) { mMicSource = micSource; }
181
182    int mapPerformanceMode(int performanceMode) {
183        int mappedPerformanceMode = -1;
184
185        // FIXME taken from OpenSLES_AndroidConfiguration.h
186        switch (performanceMode) {
187        case 0: // NONE
188        case 1: // LATENCY
189        case 2: // LATENCY_EFFECTS
190        case 3: // POWER_SAVING
191            // FIXME should use >=
192            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
193                mappedPerformanceMode = performanceMode;
194                break;
195            }
196            // fall through
197        case -1:
198        default:
199            mappedPerformanceMode = -1;
200            break;
201            }
202
203        return mappedPerformanceMode;
204    }
205
206
207    int getPerformanceMode() {
208        return mPerformanceMode;
209    }
210
211    String getPerformanceModeString(int performanceMode) {
212        String name = null;
213        String[] myArray = getResources().getStringArray(R.array.performance_mode_array);
214
215        if (myArray != null && performanceMode >= -1 && performanceMode < myArray.length - 1) {
216            name = myArray[performanceMode + 1];
217        }
218        return name;
219    }
220
221
222    void setPerformanceMode(int performanceMode) { mPerformanceMode = performanceMode; }
223
224    int getIgnoreFirstFrames() {
225        return mIgnoreFirstFrames;
226    }
227
228    void setIgnoreFirstFrames(int ignoreFirstFrames) {
229        mIgnoreFirstFrames = ignoreFirstFrames;
230    }
231
232    int getPlayerBufferSizeInBytes() {
233        return mPlayerBufferSizeInBytes;
234    }
235
236
237    void setPlayerBufferSizeInBytes(int playerBufferSizeInBytes) {
238        mPlayerBufferSizeInBytes = clamp(playerBufferSizeInBytes, Constant.PLAYER_BUFFER_FRAMES_MIN,
239                Constant.PLAYER_BUFFER_FRAMES_MAX);
240    }
241
242
243    int getRecorderBufferSizeInBytes() {
244        return mRecorderBuffSizeInBytes;
245    }
246
247
248    void setRecorderBufferSizeInBytes(int recorderBufferSizeInBytes) {
249        mRecorderBuffSizeInBytes = clamp(recorderBufferSizeInBytes,
250                Constant.RECORDER_BUFFER_FRAMES_MIN, Constant.RECORDER_BUFFER_FRAMES_MAX);
251    }
252
253
254    int getBufferTestDuration() {
255        return mBufferTestDurationInSeconds;
256    }
257
258
259    void setBufferTestDuration(int bufferTestDurationInSeconds) {
260        mBufferTestDurationInSeconds = clamp(bufferTestDurationInSeconds,
261                Constant.BUFFER_TEST_DURATION_SECONDS_MIN,
262                Constant.BUFFER_TEST_DURATION_SECONDS_MAX);
263    }
264
265
266    int getBufferTestWavePlotDuration() {
267        return mBufferTestWavePlotDurationInSeconds;
268    }
269
270
271    void setBufferTestWavePlotDuration(int bufferTestWavePlotDurationInSeconds) {
272        mBufferTestWavePlotDurationInSeconds = clamp(bufferTestWavePlotDurationInSeconds,
273                Constant.BUFFER_TEST_WAVE_PLOT_DURATION_SECONDS_MIN,
274                Constant.BUFFER_TEST_WAVE_PLOT_DURATION_SECONDS_MAX);
275    }
276
277    int getNumberOfLoadThreads() {
278        return mNumberOfLoadThreads;
279    }
280
281    void setNumberOfLoadThreads(int numberOfLoadThreads) {
282        mNumberOfLoadThreads = clamp(numberOfLoadThreads, Constant.MIN_NUM_LOAD_THREADS,
283                Constant.MAX_NUM_LOAD_THREADS);
284    }
285
286    public void setNumberOfCaptures (int num){
287        mNumStateCaptures = clamp(num, Constant.MIN_NUM_CAPTURES, Constant.MAX_NUM_CAPTURES);
288    }
289
290    public void setCaptureSysTraceEnabled (boolean enabled){
291        mCaptureSysTraceEnabled = enabled;
292    }
293
294    public void setCaptureBugreportEnabled(boolean enabled) {
295        mCaptureBugreportEnabled = enabled;
296    }
297
298    public void setCaptureWavsEnabled (boolean enabled){
299        mCaptureWavSnippetsEnabled = enabled;
300    }
301
302    public void setSoundLevelCalibrationEnabled(boolean enabled) {
303        mSoundLevelCalibrationEnabled = enabled;
304    }
305
306    public boolean isCaptureEnabled() {
307        return isCaptureSysTraceEnabled() || isCaptureBugreportEnabled();
308    }
309
310    public boolean isCaptureSysTraceEnabled() {
311        return mCaptureSysTraceEnabled;
312    }
313
314    public boolean isCaptureBugreportEnabled() {
315        return mCaptureBugreportEnabled;
316    }
317
318    public boolean isSoundLevelCalibrationEnabled() {
319        return mSoundLevelCalibrationEnabled;
320    }
321
322    public int getNumStateCaptures() {
323        return mNumStateCaptures;
324    }
325
326    public boolean isCaptureWavSnippetsEnabled() {
327        return mCaptureWavSnippetsEnabled;
328    }
329
330    /**
331     * Returns value if value is within inclusive bounds min through max
332     * otherwise returns min or max according to if value is less than or greater than the range
333     */
334    private int clamp(int value, int min, int max) {
335
336        if (max < min) throw new UnsupportedOperationException("min must be <= max");
337
338        if (value < min) return min;
339        else if (value > max) return max;
340        else return value;
341    }
342
343
344    /** Compute Default audio settings. */
345    public void computeDefaults() {
346        int samplingRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
347        setSamplingRate(samplingRate);
348
349        if (mAudioThreadType == Constant.AUDIO_THREAD_TYPE_NATIVE) {
350
351            int minBufferSizeInFrames;
352            if (isSafeToUseGetProperty()) {
353                AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
354                String value = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
355                minBufferSizeInFrames = Integer.parseInt(value);
356            } else {
357                minBufferSizeInFrames = 1024;
358                log("On button test micSource Name: ");
359            }
360            int minBufferSizeInBytes = Constant.BYTES_PER_FRAME * minBufferSizeInFrames;
361
362            setPlayerBufferSizeInBytes(minBufferSizeInBytes);
363            setRecorderBufferSizeInBytes(minBufferSizeInBytes);
364        } else {
365            int minPlayerBufferSizeInBytes = AudioTrack.getMinBufferSize(samplingRate,
366                    AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
367            setPlayerBufferSizeInBytes(minPlayerBufferSizeInBytes);
368
369            int minRecorderBufferSizeInBytes =  AudioRecord.getMinBufferSize(samplingRate,
370                    AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
371            setRecorderBufferSizeInBytes(minRecorderBufferSizeInBytes);
372        }
373
374    }
375
376
377    String getSystemInfo() {
378        String info = null;
379        try {
380            int versionCode = getApplicationContext().getPackageManager().getPackageInfo(
381                              getApplicationContext().getPackageName(), 0).versionCode;
382            String versionName = getApplicationContext().getPackageManager().getPackageInfo(
383                                 getApplicationContext().getPackageName(), 0).versionName;
384            info = "App ver. " + versionCode + "." + versionName + " | " + Build.MODEL + " | " +
385                    Build.FINGERPRINT;
386        } catch (PackageManager.NameNotFoundException e) {
387            e.printStackTrace();
388        }
389
390        return info;
391    }
392
393
394    /** Check if it's safe to use Open SLES. */
395    boolean isSafeToUseSles() {
396        return  Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
397    }
398
399
400    /** Check if it's safe to use getProperty(). */
401    boolean isSafeToUseGetProperty() {
402        return  Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
403    }
404
405
406    @Override
407    public void onConfigurationChanged(Configuration newConfig) {
408        super.onConfigurationChanged(newConfig);
409    }
410
411
412    @Override
413    public void onCreate() {
414        super.onCreate();
415
416        setDefaults();
417    }
418
419
420    @Override
421    public void onLowMemory() {
422        super.onLowMemory();
423    }
424
425
426    @Override
427    public void onTerminate() {
428        super.onTerminate();
429    }
430
431
432    private static void log(String msg) {
433        Log.v(TAG, msg);
434    }
435}
436