1 /* 2 * Copyright (C) 2009 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.effectstest; 18 19 import android.app.Activity; 20 import android.media.audiofx.AudioEffect; 21 import android.media.audiofx.BassBoost; 22 import android.os.Bundle; 23 import android.util.Log; 24 import android.view.KeyEvent; 25 import android.view.View; 26 import android.view.View.OnClickListener; 27 import android.widget.Button; 28 import android.widget.CompoundButton; 29 import android.widget.CompoundButton.OnCheckedChangeListener; 30 import android.widget.EditText; 31 import android.widget.SeekBar; 32 import android.widget.TextView; 33 import android.widget.ToggleButton; 34 35 import java.nio.ByteBuffer; 36 import java.nio.ByteOrder; 37 import java.util.HashMap; 38 39 public class BassBoostTest extends Activity implements OnCheckedChangeListener { 40 41 private final static String TAG = "BassBoostTest"; 42 43 private static int NUM_PARAMS = 1; 44 45 private EffectParameter mStrength; 46 private BassBoost mBassBoost = null; 47 ToggleButton mOnOffButton; 48 ToggleButton mReleaseButton; 49 EditText mSessionText; 50 static int sSession = 0; 51 EffectListner mEffectListener = new EffectListner(); 52 private static HashMap<Integer, BassBoost> sInstances = new HashMap<Integer, BassBoost>(10); 53 String mSettings = ""; 54 BassBoostTest()55 public BassBoostTest() { 56 Log.d(TAG, "contructor"); 57 } 58 59 @Override onCreate(Bundle icicle)60 public void onCreate(Bundle icicle) { 61 super.onCreate(icicle); 62 63 SeekBar seekBar; 64 TextView textView; 65 66 setContentView(R.layout.bassboosttest); 67 68 mSessionText = findViewById(R.id.sessionEdit); 69 mSessionText.setOnKeyListener(mSessionKeyListener); 70 71 mSessionText.setText(Integer.toString(sSession)); 72 73 mReleaseButton = (ToggleButton)findViewById(R.id.bbReleaseButton); 74 mOnOffButton = (ToggleButton)findViewById(R.id.bassboostOnOff); 75 76 final Button hammerReleaseTest = (Button) findViewById(R.id.hammer_on_release_bug); 77 hammerReleaseTest.setEnabled(false); 78 79 getEffect(sSession); 80 81 if (mBassBoost != null) { 82 mReleaseButton.setOnCheckedChangeListener(this); 83 mOnOffButton.setOnCheckedChangeListener(this); 84 85 textView = (TextView)findViewById(R.id.bbStrengthMin); 86 textView.setText("0"); 87 textView = (TextView)findViewById(R.id.bbStrengthMax); 88 textView.setText("1000"); 89 seekBar = (SeekBar)findViewById(R.id.bbStrengthSeekBar); 90 textView = (TextView)findViewById(R.id.bbStrengthValue); 91 mStrength = new BassBoostParam(mBassBoost, 0, 1000, seekBar, textView); 92 seekBar.setOnSeekBarChangeListener(mStrength); 93 mStrength.setEnabled(mBassBoost.getStrengthSupported()); 94 95 hammerReleaseTest.setEnabled(true); 96 hammerReleaseTest.setOnClickListener(new OnClickListener() { 97 @Override 98 public void onClick(View v) { 99 runHammerReleaseTest(hammerReleaseTest); 100 } 101 }); 102 } 103 } 104 105 @Override onResume()106 public void onResume() { 107 super.onResume(); 108 } 109 110 @Override onPause()111 public void onPause() { 112 super.onPause(); 113 } 114 115 private View.OnKeyListener mSessionKeyListener 116 = new View.OnKeyListener() { 117 public boolean onKey(View v, int keyCode, KeyEvent event) { 118 Log.d(TAG, "onKey() keyCode: "+keyCode+" event.getAction(): "+event.getAction()); 119 if (event.getAction() == KeyEvent.ACTION_DOWN) { 120 switch (keyCode) { 121 case KeyEvent.KEYCODE_DPAD_CENTER: 122 case KeyEvent.KEYCODE_ENTER: 123 try { 124 sSession = Integer.parseInt(mSessionText.getText().toString()); 125 getEffect(sSession); 126 if (mBassBoost != null) { 127 mStrength.setEffect(mBassBoost); 128 mStrength.setEnabled(mBassBoost.getStrengthSupported()); 129 } 130 } catch (NumberFormatException e) { 131 Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString()); 132 } 133 return true; 134 } 135 } 136 return false; 137 } 138 }; 139 140 // OnCheckedChangeListener onCheckedChanged(CompoundButton buttonView, boolean isChecked)141 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 142 if (buttonView.getId() == R.id.bassboostOnOff) { 143 if (mBassBoost != null) { 144 mBassBoost.setEnabled(isChecked); 145 mStrength.updateDisplay(); 146 } 147 } 148 if (buttonView.getId() == R.id.bbReleaseButton) { 149 if (isChecked) { 150 if (mBassBoost == null) { 151 getEffect(sSession); 152 if (mBassBoost != null) { 153 mStrength.setEffect(mBassBoost); 154 mStrength.setEnabled(mBassBoost.getStrengthSupported()); 155 } 156 } 157 } else { 158 if (mBassBoost != null) { 159 mStrength.setEnabled(false); 160 putEffect(sSession); 161 } 162 } 163 } 164 } 165 166 private class BassBoostParam extends EffectParameter { 167 private BassBoost mBassBoost; 168 BassBoostParam(BassBoost bassboost, int min, int max, SeekBar seekBar, TextView textView)169 public BassBoostParam(BassBoost bassboost, int min, int max, SeekBar seekBar, TextView textView) { 170 super (min, max, seekBar, textView, "o/oo"); 171 172 mBassBoost = bassboost; 173 updateDisplay(); 174 } 175 176 @Override setParameter(Integer value)177 public void setParameter(Integer value) { 178 if (mBassBoost != null) { 179 mBassBoost.setStrength(value.shortValue()); 180 } 181 } 182 183 @Override getParameter()184 public Integer getParameter() { 185 if (mBassBoost != null) { 186 return new Integer(mBassBoost.getRoundedStrength()); 187 } 188 return new Integer(0); 189 } 190 191 @Override setEffect(Object effect)192 public void setEffect(Object effect) { 193 mBassBoost = (BassBoost)effect; 194 } 195 } 196 197 public class EffectListner implements AudioEffect.OnEnableStatusChangeListener, 198 AudioEffect.OnControlStatusChangeListener, AudioEffect.OnParameterChangeListener 199 { EffectListner()200 public EffectListner() { 201 } onEnableStatusChange(AudioEffect effect, boolean enabled)202 public void onEnableStatusChange(AudioEffect effect, boolean enabled) { 203 Log.d(TAG,"onEnableStatusChange: "+ enabled); 204 } onControlStatusChange(AudioEffect effect, boolean controlGranted)205 public void onControlStatusChange(AudioEffect effect, boolean controlGranted) { 206 Log.d(TAG,"onControlStatusChange: "+ controlGranted); 207 } onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value)208 public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { 209 int p = byteArrayToInt(param, 0); 210 short v = byteArrayToShort(value, 0); 211 212 Log.d(TAG,"onParameterChange, status: "+status+" p: "+p+" v: "+v); 213 } 214 byteArrayToInt(byte[] valueBuf, int offset)215 private int byteArrayToInt(byte[] valueBuf, int offset) { 216 ByteBuffer converter = ByteBuffer.wrap(valueBuf); 217 converter.order(ByteOrder.nativeOrder()); 218 return converter.getInt(offset); 219 220 } byteArrayToShort(byte[] valueBuf, int offset)221 private short byteArrayToShort(byte[] valueBuf, int offset) { 222 ByteBuffer converter = ByteBuffer.wrap(valueBuf); 223 converter.order(ByteOrder.nativeOrder()); 224 return converter.getShort(offset); 225 226 } 227 228 } 229 getEffect(int session)230 private void getEffect(int session) { 231 synchronized (sInstances) { 232 if (sInstances.containsKey(session)) { 233 mBassBoost = sInstances.get(session); 234 } else { 235 try{ 236 mBassBoost = new BassBoost(0, session); 237 } catch (IllegalArgumentException e) { 238 Log.e(TAG,"BassBoost effect not supported"); 239 } catch (IllegalStateException e) { 240 Log.e(TAG,"BassBoost cannot get strength supported"); 241 } catch (UnsupportedOperationException e) { 242 Log.e(TAG,"BassBoost library not loaded"); 243 } catch (RuntimeException e) { 244 Log.e(TAG,"BassBoost effect not found"); 245 } 246 sInstances.put(session, mBassBoost); 247 } 248 mReleaseButton.setEnabled(false); 249 mOnOffButton.setEnabled(false); 250 251 if (mBassBoost != null) { 252 if (mSettings != "") { 253 mBassBoost.setProperties(new BassBoost.Settings(mSettings)); 254 } 255 mBassBoost.setEnableStatusListener(mEffectListener); 256 mBassBoost.setControlStatusListener(mEffectListener); 257 mBassBoost.setParameterListener(mEffectListener); 258 259 mReleaseButton.setChecked(true); 260 mReleaseButton.setEnabled(true); 261 262 mOnOffButton.setChecked(mBassBoost.getEnabled()); 263 mOnOffButton.setEnabled(true); 264 } 265 } 266 } 267 putEffect(int session)268 private void putEffect(int session) { 269 mOnOffButton.setChecked(false); 270 mOnOffButton.setEnabled(false); 271 synchronized (sInstances) { 272 if (mBassBoost != null) { 273 mSettings = mBassBoost.getProperties().toString(); 274 mBassBoost.release(); 275 Log.d(TAG,"BassBoost released"); 276 mBassBoost = null; 277 sInstances.remove(session); 278 } 279 } 280 } 281 282 // Stress-tests releasing of AudioEffect by doing repeated creation 283 // and subsequent releasing. Also forces emission of callbacks from 284 // the AudioFlinger by setting a control status listener. Since all 285 // effect instances are bound to the same session, the AF will 286 // notify them about the change in their status. This can reveal racy 287 // behavior w.r.t. releasing. 288 class HammerReleaseTest extends Thread { 289 private static final int NUM_EFFECTS = 10; 290 private static final int NUM_ITERATIONS = 100; 291 private final int mSession; 292 private final Runnable mOnComplete; 293 HammerReleaseTest(int session, Runnable onComplete)294 HammerReleaseTest(int session, Runnable onComplete) { 295 mSession = session; 296 mOnComplete = onComplete; 297 } 298 299 @Override run()300 public void run() { 301 Log.w(TAG, "HammerReleaseTest started"); 302 BassBoost[] effects = new BassBoost[NUM_EFFECTS]; 303 for (int i = 0; i < NUM_ITERATIONS; i++) { 304 for (int j = 0; j < NUM_EFFECTS; j++) { 305 effects[j] = new BassBoost(0, mSession); 306 effects[j].setControlStatusListener(mEffectListener); 307 yield(); 308 } 309 for (int j = NUM_EFFECTS - 1; j >= 0; j--) { 310 Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]); 311 effects[j].release(); 312 effects[j] = null; 313 yield(); 314 } 315 } 316 Log.w(TAG, "HammerReleaseTest ended"); 317 runOnUiThread(mOnComplete); 318 } 319 } 320 runHammerReleaseTest(Button controlButton)321 private void runHammerReleaseTest(Button controlButton) { 322 controlButton.setEnabled(false); 323 HammerReleaseTest thread = new HammerReleaseTest(sSession, 324 () -> { 325 controlButton.setEnabled(true); 326 }); 327 thread.start(); 328 } 329 330 } 331