• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.cts.verifier.audioquality;
18 
19 import com.android.cts.verifier.R;
20 
21 import android.app.Activity;
22 import android.media.AudioFormat;
23 import android.media.AudioRecord;
24 import android.media.MediaRecorder;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.util.Log;
29 import android.view.View;
30 import android.widget.Button;
31 import android.widget.ProgressBar;
32 import android.widget.TextView;
33 
34 /**
35  * Play a continuous sound and allow the user to monitor the sound level
36  * at the mic in real time, relative to the range the phone can detect.
37  * This is not an absolute sound level meter, but lets the speaker volume
38  * and position be adjusted so that the clipping point is known.
39  */
40 public class CalibrateVolumeActivity extends Activity implements View.OnClickListener {
41     public static final String TAG = "AudioQualityVerifier";
42 
43     public static final int OUTPUT_AMPL = 5000;
44     public static final float TARGET_RMS = 5000.0f;
45     public static final float TARGET_AMPL = (float) (TARGET_RMS * Math.sqrt(2.0));
46     private static final float FREQ = 625.0f;
47 
48     private static final float TOLERANCE = 1.03f;
49 
50     private static final int DEBOUNCE_TIME = 500; // Minimum time in ms between status text changes
51     public static final boolean USE_PINK = false;
52 
53     private ProgressBar mSlider;
54     private Button mDoneButton;
55     private TextView mStatus;
56     private BackgroundAudio mBackgroundAudio;
57     private Monitor mMonitor;
58     private Handler mHandler;
59 
60     private Native mNative;
61 
62     enum Status { LOW, OK, HIGH, UNDEFINED }
63     private static int[] mStatusText = {
64         R.string.aq_status_low, R.string.aq_status_ok, R.string.aq_status_high };
65 
66     @Override
onCreate(Bundle savedInstanceState)67     public void onCreate(Bundle savedInstanceState) {
68         super.onCreate(savedInstanceState);
69         setContentView(R.layout.aq_sound_level_meter);
70 
71         mSlider = (ProgressBar) findViewById(R.id.slider);
72         mStatus = (TextView) findViewById(R.id.status);
73         mStatus.setText(R.string.aq_status_unknown);
74 
75         mDoneButton = (Button) findViewById(R.id.doneButton);
76         mDoneButton.setOnClickListener(this);
77 
78         mNative = Native.getInstance();
79         mHandler = new UpdateHandler();
80     }
81 
82     // Implements View.OnClickListener
onClick(View v)83     public void onClick(View v) {
84         if (v == mDoneButton) {
85             finish();
86         }
87     }
88 
89     @Override
onResume()90     protected void onResume() {
91         super.onResume();
92         final int DURATION = 1;
93         final float RAMP = 0.0f;
94         byte[] noise;
95         if (USE_PINK) {
96             noise = Utils.getPinkNoise(this, OUTPUT_AMPL, DURATION);
97         } else {
98             short[] sinusoid = mNative.generateSinusoid(FREQ, DURATION,
99                     AudioQualityVerifierActivity.SAMPLE_RATE, OUTPUT_AMPL, RAMP);
100             noise = Utils.shortToByteArray(sinusoid);
101         }
102         float[] results = mNative.measureRms(Utils.byteToShortArray(noise),
103                 AudioQualityVerifierActivity.SAMPLE_RATE, -1.0f);
104         float rms = results[Native.MEASURE_RMS_RMS];
105         Log.i(TAG, "Stimulus amplitude " + OUTPUT_AMPL + ", RMS " + rms);
106         mBackgroundAudio = new BackgroundAudio(noise);
107         mMonitor = new Monitor();
108     }
109 
110     @Override
onPause()111     protected void onPause() {
112         super.onPause();
113         mBackgroundAudio.halt();
114         mMonitor.halt();
115     }
116 
117     private class UpdateHandler extends Handler {
118         private Status mState = Status.UNDEFINED;
119         private long mTimestamp = 0; // Time of last status change in ms
120 
121         @Override
handleMessage(Message msg)122         public void handleMessage(Message msg) {
123             int rms = msg.arg1;
124             int max = mSlider.getMax();
125             int progress = (max / 2 * rms) / Math.round(TARGET_RMS);
126             if (progress > max) progress = max;
127             mSlider.setProgress(progress);
128 
129             Status state;
130             if (rms * TOLERANCE < TARGET_RMS) state = Status.LOW;
131             else if (rms > TARGET_RMS * TOLERANCE) state = Status.HIGH;
132             else state = Status.OK;
133             if (state != mState) {
134                 long timestamp = System.currentTimeMillis();
135                 if (timestamp - mTimestamp > DEBOUNCE_TIME) {
136                     mStatus.setText(mStatusText[state.ordinal()]);
137                     mState = state;
138                     mTimestamp = timestamp;
139                 }
140             }
141         }
142     }
143 
144     class Monitor extends Thread {
145         private static final int BUFFER_TIME = 100; // Min time in ms to buffer for
146         private static final int READ_TIME = 25;    // Max length of time in ms to read in one chunk
147         private static final boolean DEBUG = false;
148 
149         private AudioRecord mRecord;
150         private int mSamplesToRead;
151         private byte[] mBuffer;
152         private boolean mProceed;
153 
Monitor()154         Monitor() {
155             mProceed = true;
156 
157             mSamplesToRead = (READ_TIME * AudioQualityVerifierActivity.SAMPLE_RATE) / 1000;
158             mBuffer = new byte[mSamplesToRead * AudioQualityVerifierActivity.BYTES_PER_SAMPLE];
159 
160             final int minBufferSize = (BUFFER_TIME * AudioQualityVerifierActivity.SAMPLE_RATE *
161                     AudioQualityVerifierActivity.BYTES_PER_SAMPLE) / 1000;
162             final int bufferSize = Utils.getAudioRecordBufferSize(minBufferSize);
163 
164             mRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_RECOGNITION,
165                     AudioQualityVerifierActivity.SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
166                     AudioQualityVerifierActivity.AUDIO_FORMAT, bufferSize);
167             if (mRecord.getState() != AudioRecord.STATE_INITIALIZED) {
168                 Log.e(TAG, "Couldn't open audio for recording");
169                 return;
170             }
171             mRecord.startRecording();
172             if (mRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
173                 Log.e(TAG, "Couldn't record");
174                 return;
175             }
176 
177             start(); // Begin background thread
178         }
179 
halt()180         public void halt() {
181             mProceed = false;
182         }
183 
184         @Override
run()185         public void run() {
186             int maxBytes = mSamplesToRead * AudioQualityVerifierActivity.BYTES_PER_SAMPLE;
187             int bytes;
188             while (true) {
189                 if (!mProceed) {
190                     mRecord.stop();
191                     mRecord.release();
192                     return; // End thread
193                 }
194                 bytes = mRecord.read(mBuffer, 0, maxBytes);
195                 if (bytes < 0) {
196                     if (bytes == AudioRecord.ERROR_INVALID_OPERATION) {
197                         Log.e(TAG, "Recording object not initalized");
198                     } else if (bytes == AudioRecord.ERROR_BAD_VALUE) {
199                         Log.e(TAG, "Invalid recording parameters");
200                     } else {
201                         Log.e(TAG, "Error during recording");
202                     }
203                     return;
204                 }
205                 if (bytes >= 2) {
206                     // Note: this won't work well if bytes is small (we should check)
207                     short[] samples = Utils.byteToShortArray(mBuffer, 0, bytes);
208                     float[] results = mNative.measureRms(samples,
209                             AudioQualityVerifierActivity.SAMPLE_RATE, -1.0f);
210                     float rms = results[Native.MEASURE_RMS_RMS];
211                     float duration = results[Native.MEASURE_RMS_DURATION];
212                     float mean = results[Native.MEASURE_RMS_MEAN];
213                     if (DEBUG) {
214                         // Confirm the RMS calculation
215                         float verifyRms = 0.0f;
216                         for (short x : samples) {
217                             verifyRms += x * x;
218                         }
219                         verifyRms /= samples.length;
220                         Log.i(TAG, "RMS: " + rms + ", bytes: " + bytes
221                                 + ", duration: " + duration + ", mean: " + mean
222                                 + ", manual RMS: " + Math.sqrt(verifyRms));
223                     }
224                     Message msg = mHandler.obtainMessage();
225                     msg.arg1 = Math.round(rms);
226                     mHandler.sendMessage(msg);
227                 }
228             }
229         }
230     }
231 }
232