package com.simplecity.amp_library.services;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.audiofx.AudioEffect;
import android.media.audiofx.BassBoost;
import android.media.audiofx.Equalizer;
import android.media.audiofx.Virtualizer;
import android.os.Binder;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import com.simplecity.amp_library.utils.SettingsManager;
import java.util.concurrent.ConcurrentHashMap;
/**
* <p>This calls listen to events that affect DSP function and responds to them.</p>
* <ol>
* <li>new audio session declarations</li>
* <li>headset plug / unplug events</li>
* <li>preference update events.</li>
* </ol>
*
* @author alankila
*/
public class EqualizerService extends Service {
public static final String ACTION_OPEN_EQUALIZER_SESSION = "com.simplecity.amp_library.audiofx.OPEN_SESSION";
public static final String ACTION_CLOSE_EQUALIZER_SESSION = "com.simplecity.amp_library.audiofx.CLOSE_SESSION";
private SharedPreferences mPrefs;
public static String getZeroedBandsString(int length) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < length; i++) {
stringBuilder.append("0;");
}
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
return stringBuilder.toString();
}
/**
* Helper class representing the full complement of effects attached to one
* audio session.
*
* @author alankila
*/
private static class EffectSet {
/**
* Session-specific equalizer
*/
Equalizer equalizer;
/**
* Session-specific bassboost
*/
private BassBoost bassBoost;
/**
* Session-specific virtualizer
*/
private Virtualizer virtualizer;
// private final PresetReverb mPresetReverb;
private short mEqNumPresets = -1;
private short mEqNumBands = -1;
public EffectSet(int sessionId) {
equalizer = new Equalizer(1, sessionId);
bassBoost = new BassBoost(1, sessionId);
virtualizer = new Virtualizer(1, sessionId);
// mPresetReverb = new PresetReverb(0, sessionId);
}
/*
* Take lots of care to not poke values that don't need
* to be poked- this can cause audible pops.
*/
public void enableEqualizer(boolean enable) {
if (enable != equalizer.getEnabled()) {
if (!enable) {
for (short i = 0; i < getNumEqualizerBands(); i++) {
equalizer.setBandLevel(i, (short) 0);
}
}
equalizer.setEnabled(enable);
}
}
public void setEqualizerLevels(short[] levels) {
if (equalizer.getEnabled()) {
for (short i = 0; i < levels.length; i++) {
if (equalizer.getBandLevel(i) != levels[i]) {
equalizer.setBandLevel(i, levels[i]);
}
}
}
}
public short getNumEqualizerBands() {
if (mEqNumBands < 0) {
mEqNumBands = equalizer.getNumberOfBands();
}
if (mEqNumBands > 6) {
mEqNumBands = 6;
}
return mEqNumBands;
}
public short getNumEqualizerPresets() {
if (mEqNumPresets < 0) {
mEqNumPresets = equalizer.getNumberOfPresets();
}
return mEqNumPresets;
}
public void enableBassBoost(boolean enable) {
if (enable != bassBoost.getEnabled()) {
if (!enable) {
bassBoost.setStrength((short) 1);
bassBoost.setStrength((short) 0);
}
bassBoost.setEnabled(enable);
}
}
public void setBassBoostStrength(short strength) {
if (bassBoost.getEnabled() && bassBoost.getRoundedStrength() != strength) {
bassBoost.setStrength(strength);
}
}
public void enableVirtualizer(boolean enable) {
if (enable != virtualizer.getEnabled()) {
if (!enable) {
virtualizer.setStrength((short) 1);
virtualizer.setStrength((short) 0);
}
virtualizer.setEnabled(enable);
}
}
public void setVirtualizerStrength(short strength) {
if (virtualizer.getEnabled() && virtualizer.getRoundedStrength() != strength) {
virtualizer.setStrength(strength);
}
}
// public void enableReverb(boolean enable) {
// if (enable != mPresetReverb.getEnabled()) {
// if (!enable) {
// mPresetReverb.setPreset((short) 0);
// }
// mPresetReverb.setEnabled(enable);
// }
// }
// public void setReverbPreset(short preset) {
// if (mPresetReverb.getEnabled() && mPresetReverb.getPreset() != preset) {
// mPresetReverb.setPreset(preset);
// }
// }
public void release() {
equalizer.release();
bassBoost.release();
virtualizer.release();
// mPresetReverb.release();
}
}
protected static final String TAG = EqualizerService.class.getSimpleName();
public class LocalBinder extends Binder {
public EqualizerService getService() {
return EqualizerService.this;
}
}
private final LocalBinder mBinder = new LocalBinder();
/**
* Known audio sessions and their associated audioeffect suites.
*/
protected final ConcurrentHashMap<Integer, EffectSet> mAudioSessions = new ConcurrentHashMap<>();
/**
* Receive new broadcast intents for adding DSP to session
*/
private final BroadcastReceiver mAudioSessionReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
int sessionId = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION, 0);
if (action.equals(ACTION_OPEN_EQUALIZER_SESSION)) {
// Log.i(TAG, "Open session called. Session: " + sessionId);
if (!mAudioSessions.containsKey(sessionId)) {
try {
EffectSet effectSet = new EffectSet(sessionId);
mAudioSessions.put(sessionId, effectSet);
} catch (Exception | ExceptionInInitializerError e) {
Log.e(TAG, "Failed to open EQ session.. EffectSet error " + e);
}
}
}
if (action.equals(ACTION_CLOSE_EQUALIZER_SESSION)) {
// Log.i(TAG, "Close session called");
EffectSet gone = mAudioSessions.remove(sessionId);
if (gone != null) {
gone.release();
}
}
update();
}
};
@Override
public void onCreate() {
super.onCreate();
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
IntentFilter audioFilter = new IntentFilter();
audioFilter.addAction(ACTION_OPEN_EQUALIZER_SESSION);
audioFilter.addAction(ACTION_CLOSE_EQUALIZER_SESSION);
registerReceiver(mAudioSessionReceiver, audioFilter);
saveDefaults();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
unregisterReceiver(mAudioSessionReceiver);
for (EffectSet gone : mAudioSessions.values()) {
if (gone != null) {
gone.release();
}
}
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private void saveDefaults() {
EffectSet temp;
try {
temp = new EffectSet(0);
} catch (Exception | ExceptionInInitializerError | UnsatisfiedLinkError e) {
// this is really bad- likely the media stack is broken.
// disable ourself if we get into this state, as the service
// will restart itself repeatedly!
Log.e(TAG, e.getMessage(), e);
stopSelf();
return;
}
final int numBands = temp.getNumEqualizerBands();
final int numPresets = temp.getNumEqualizerPresets();
SharedPreferences.Editor editor = mPrefs.edit();
editor.putString("equalizer.number_of_presets", String.valueOf(numPresets)).apply();
editor.putString("equalizer.number_of_bands", String.valueOf(numBands)).apply();
// range
short[] rangeShortArr = temp.equalizer.getBandLevelRange();
editor.putString("equalizer.band_level_range", rangeShortArr[0] + ";" + rangeShortArr[1]).apply();
// center freqs
StringBuilder centerFreqs = new StringBuilder();
// audiofx.global.centerfreqs
for (short i = 0; i < numBands; i++) {
centerFreqs.append(temp.equalizer.getCenterFreq(i));
centerFreqs.append(";");
}
centerFreqs.deleteCharAt(centerFreqs.length() - 1);
editor.putString("equalizer.center_freqs", centerFreqs.toString()).apply();
// populate preset names
StringBuilder presetNames = new StringBuilder();
for (int i = 0; i < numPresets; i++) {
String presetName = temp.equalizer.getPresetName((short) i);
presetNames.append(presetName);
presetNames.append("|");
// populate preset band values
StringBuilder presetBands = new StringBuilder();
try {
temp.equalizer.usePreset((short) i);
} catch (RuntimeException e) {
Log.e(TAG, "equalizer.usePreset() failed");
}
for (int j = 0; j < numBands; j++) {
// loop through preset bands
presetBands.append(temp.equalizer.getBandLevel((short) j));
presetBands.append(";");
}
presetBands.deleteCharAt(presetBands.length() - 1);
editor.putString("equalizer.preset." + i, presetBands.toString()).apply();
}
if (presetNames.length() != 0) {
presetNames.deleteCharAt(presetNames.length() - 1);
editor.putString("equalizer.preset_names", presetNames.toString()).apply();
}
temp.release();
}
/**
* Push new configuration to audio stack.
*/
public synchronized void update() {
for (Integer sessionId : mAudioSessions.keySet()) {
updateDsp(mAudioSessions.get(sessionId));
}
}
private void updateDsp(EffectSet session) {
final boolean globalEnabled = SettingsManager.getInstance().getEqualizerEnabled();
try {
session.enableBassBoost(globalEnabled && mPrefs.getBoolean("audiofx.bass.enable", false));
session.setBassBoostStrength(Short.valueOf(mPrefs.getString("audiofx.bass.strength", "0")));
} catch (Exception e) {
Log.e(TAG, "Error enabling bass boost!", e);
}
// try {
// short preset = Short.decode(mPrefs.getString("audiofx.reverb.preset", String.valueOf(PresetReverb.PRESET_NONE)));
// session.enableReverb(globalEnabled && (preset > 0));
// session.setReverbPreset(preset);
//
// } catch (Exception e) {
// Log.e(TAG, "Error enabling reverb preset", e);
// }
try {
session.enableEqualizer(globalEnabled);
final int customPresetPos = session.getNumEqualizerPresets();
final int preset = Integer.valueOf(mPrefs.getString("audiofx.eq.preset", String.valueOf(customPresetPos)));
final int bands = session.getNumEqualizerBands();
/*
* Equalizer state is in a single string preference with all values
* separated by ;
*/
String[] levels;
if (preset == customPresetPos) {
levels = mPrefs.getString("audiofx.eq.bandlevels.custom", getZeroedBandsString(bands)).split(";");
} else {
levels = mPrefs.getString("equalizer.preset." + preset, getZeroedBandsString(bands)).split(";");
}
short[] equalizerLevels = new short[levels.length];
for (int i = 0; i < levels.length; i++) {
equalizerLevels[i] = Short.parseShort(levels[i]);
}
session.setEqualizerLevels(equalizerLevels);
} catch (Exception e) {
Log.e(TAG, "Error enabling equalizer!", e);
}
try {
session.enableVirtualizer(globalEnabled && mPrefs.getBoolean("audiofx.virtualizer.enable", false));
session.setVirtualizerStrength(Short.valueOf(mPrefs.getString("audiofx.virtualizer.strength", "0")));
} catch (Exception e) {
Log.e(TAG, "Error enabling virtualizer!");
}
}
/**
* Sends a broadcast to close any existing audio effect sessions
*/
public static void closeEqualizerSessions(Context context, boolean internal, int audioSessionId) {
if (internal) {
//Close the internal audio session
Intent intent = new Intent(EqualizerService.ACTION_CLOSE_EQUALIZER_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId);
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
context.sendBroadcast(intent);
} else {
Intent intent = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId);
context.sendBroadcast(intent);
//Close any external audio sessions on session 0
intent = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, 0);
context.sendBroadcast(intent);
}
}
public static void openEqualizerSession(Context context, boolean internal, int audioSessionId) {
final Intent intent = new Intent();
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId);
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
intent.setAction(internal ? EqualizerService.ACTION_OPEN_EQUALIZER_SESSION : AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
context.sendBroadcast(intent);
}
}