CSD: Implement support for absolute volume

Devices with absolute volume will query the volume curves and apply the
given attenuation on the internal MEL computation.

Test: dumpsys and logs
Bug: 265928284
Change-Id: Ia126547e40cad0d8abdf740d7880502a5c82c36f
Merged-In: Ia126547e40cad0d8abdf740d7880502a5c82c36f
This commit is contained in:
Vlad Popa
2023-02-01 12:03:09 +01:00
parent f062919332
commit 2c7e91b8a6
2 changed files with 138 additions and 45 deletions

View File

@@ -8358,6 +8358,7 @@ public class AudioService extends IAudioService.Stub
synchronized (VolumeStreamState.class) {
// apply device specific volumes first
int index;
boolean isAbsoluteVolume = false;
for (int i = 0; i < mIndexMap.size(); i++) {
final int device = mIndexMap.keyAt(i);
if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
@@ -8366,6 +8367,7 @@ public class AudioService extends IAudioService.Stub
} else if (isAbsoluteVolumeDevice(device)
|| isA2dpAbsoluteVolumeDevice(device)
|| AudioSystem.isLeAudioDeviceType(device)) {
isAbsoluteVolume = true;
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
} else if (isFullVolumeDevice(device)) {
index = (mIndexMax + 5)/10;
@@ -8374,6 +8376,11 @@ public class AudioService extends IAudioService.Stub
} else {
index = (mIndexMap.valueAt(i) + 5)/10;
}
sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION,
SENDMSG_REPLACE, device, isAbsoluteVolume ? 1 : 0, this,
/*delay=*/0);
setStreamVolumeIndex(index, device);
}
}
@@ -8848,6 +8855,10 @@ public class AudioService extends IAudioService.Stub
/*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
synchronized (VolumeStreamState.class) {
sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, SENDMSG_REPLACE,
device, (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device)
|| AudioSystem.isLeAudioDeviceType(device) ? 1 : 0),
streamState, /*delay=*/0);
// Apply volume
streamState.applyDeviceVolume_syncVSS(device);

View File

@@ -56,6 +56,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
@@ -89,6 +90,7 @@ public class SoundDoseHelper {
private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 2;
private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 3;
private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 4;
/*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 5;
private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
@@ -142,6 +144,13 @@ public class SoundDoseHelper {
Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET));
private final Set<Integer> mSafeMediaCsdDevices = new HashSet<>(
Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET,
AudioSystem.DEVICE_OUT_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_BROADCAST,
AudioSystem.DEVICE_OUT_HEARING_AID,
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
// mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
// When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
@@ -158,10 +167,10 @@ public class SoundDoseHelper {
private final boolean mEnableCsd;
private ISoundDose mSoundDose;
private final Object mCsdStateLock = new Object();
private final AtomicReference<ISoundDose> mSoundDose = new AtomicReference<>();
@GuardedBy("mCsdStateLock")
private float mCurrentCsd = 0.f;
// dose at which the next dose reached warning occurs
@@ -245,9 +254,11 @@ public class SoundDoseHelper {
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
} else {
mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0);
Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
}
initCsd();
// The default safe volume index read here will be replaced by the actual value when
// the mcc is read by onConfigureSafeMedia()
// For now we use the same index for RS2 initial warning with CSD
@@ -263,9 +274,14 @@ public class SoundDoseHelper {
return 0.f;
}
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
final ISoundDose soundDose = mSoundDose.get();
if (soundDose == null) {
Log.w(TAG, "Sound dose interface not initialized");
return 0.f;
}
try {
return mSoundDose.getOutputRs2();
return soundDose.getOutputRs2();
} catch (RemoteException e) {
Log.e(TAG, "Exception while getting the RS2 exposure value", e);
return 0.f;
@@ -277,9 +293,14 @@ public class SoundDoseHelper {
return;
}
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
final ISoundDose soundDose = mSoundDose.get();
if (soundDose == null) {
Log.w(TAG, "Sound dose interface not initialized");
return;
}
try {
mSoundDose.setOutputRs2(rs2Value);
soundDose.setOutputRs2(rs2Value);
} catch (RemoteException e) {
Log.e(TAG, "Exception while setting the RS2 exposure value", e);
}
@@ -290,9 +311,14 @@ public class SoundDoseHelper {
return -1.f;
}
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
final ISoundDose soundDose = mSoundDose.get();
if (soundDose == null) {
Log.w(TAG, "Sound dose interface not initialized");
return -1.f;
}
try {
return mSoundDose.getCsd();
return soundDose.getCsd();
} catch (RemoteException e) {
Log.e(TAG, "Exception while getting the CSD value", e);
return -1.f;
@@ -304,13 +330,18 @@ public class SoundDoseHelper {
return;
}
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
final ISoundDose soundDose = mSoundDose.get();
if (soundDose == null) {
Log.w(TAG, "Sound dose interface not initialized");
return;
}
try {
final SoundDoseRecord record = new SoundDoseRecord();
record.timestamp = System.currentTimeMillis();
record.value = csd;
final SoundDoseRecord[] recordArray = new SoundDoseRecord[] { record };
mSoundDose.resetCsd(csd, recordArray);
soundDose.resetCsd(csd, recordArray);
} catch (RemoteException e) {
Log.e(TAG, "Exception while setting the CSD value", e);
}
@@ -321,9 +352,14 @@ public class SoundDoseHelper {
return;
}
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
final ISoundDose soundDose = mSoundDose.get();
if (soundDose == null) {
Log.w(TAG, "Sound dose interface not initialized");
return;
}
try {
mSoundDose.forceUseFrameworkMel(useFrameworkMel);
soundDose.forceUseFrameworkMel(useFrameworkMel);
} catch (RemoteException e) {
Log.e(TAG, "Exception while forcing the internal MEL computation", e);
}
@@ -334,9 +370,14 @@ public class SoundDoseHelper {
return;
}
Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
final ISoundDose soundDose = mSoundDose.get();
if (soundDose == null) {
Log.w(TAG, "Sound dose interface not initialized");
return;
}
try {
mSoundDose.forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
soundDose.forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
} catch (RemoteException e) {
Log.e(TAG, "Exception while forcing CSD computation on all devices", e);
}
@@ -551,6 +592,15 @@ public class SoundDoseHelper {
case MSG_PERSIST_CSD_VALUES:
onPersistSoundDoseRecords();
break;
case MSG_CSD_UPDATE_ATTENUATION:
final int device = msg.arg1;
final boolean isAbsoluteVolume = (msg.arg2 == 1);
final AudioService.VolumeStreamState streamState =
(AudioService.VolumeStreamState) msg.obj;
updateDoseAttenuation(streamState.getIndex(device), device,
streamState.getStreamType(), isAbsoluteVolume);
break;
default:
Log.e(TAG, "Unexpected msg to handle: " + msg.what);
break;
@@ -574,16 +624,18 @@ public class SoundDoseHelper {
/*package*/void reset() {
Log.d(TAG, "Reset the sound dose helper");
mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback));
synchronized (mCsdStateLock) {
try {
if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) {
final ISoundDose soundDose = mSoundDose.get();
if (soundDose != null && soundDose.asBinder().isBinderAlive()) {
if (mCurrentCsd != 0.f) {
Log.d(TAG,
"Resetting the saved sound dose value " + mCurrentCsd);
SoundDoseRecord[] records = mDoseRecords.toArray(
new SoundDoseRecord[0]);
mSoundDose.resetCsd(mCurrentCsd, records);
soundDose.resetCsd(mCurrentCsd, records);
}
}
} catch (RemoteException e) {
@@ -592,36 +644,70 @@ public class SoundDoseHelper {
}
}
private void initCsd() {
if (mEnableCsd) {
Log.v(TAG, "Initializing sound dose");
private void updateDoseAttenuation(int newIndex, int device, int streamType,
boolean isAbsoluteVolume) {
if (!mEnableCsd) {
return;
}
synchronized (mCsdStateLock) {
if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) {
mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L;
}
final ISoundDose soundDose = mSoundDose.get();
if (soundDose == null) {
Log.w(TAG, "Can not apply attenuation. ISoundDose itf is null.");
return;
}
float prevCsd = mCurrentCsd;
// Restore persisted values
mCurrentCsd = parseGlobalSettingFloat(
Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE, /* defaultValue= */0.f);
if (mCurrentCsd != prevCsd) {
mNextCsdWarning = parseGlobalSettingFloat(
Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING, /* defaultValue= */1.f);
final List<SoundDoseRecord> records = persistedStringToRecordList(
mSettings.getGlobalString(mAudioService.getContentResolver(),
Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS),
mGlobalTimeOffsetInSecs);
if (records != null) {
mDoseRecords.addAll(records);
}
}
try {
if (!isAbsoluteVolume) {
// remove any possible previous attenuation
soundDose.updateAttenuation(/* attenuationDB= */0.f, device);
return;
}
reset();
if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
&& mSafeMediaCsdDevices.contains(device)) {
soundDose.updateAttenuation(
AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
(newIndex + 5) / 10,
device), device);
}
} catch (RemoteException e) {
Log.e(TAG, "Could not apply the attenuation for MEL calculation with volume index "
+ newIndex, e);
}
}
private void initCsd() {
if (!mEnableCsd) {
return;
}
Log.v(TAG, "Initializing sound dose");
synchronized (mCsdStateLock) {
if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) {
mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L;
}
float prevCsd = mCurrentCsd;
// Restore persisted values
mCurrentCsd = parseGlobalSettingFloat(
Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE, /* defaultValue= */0.f);
if (mCurrentCsd != prevCsd) {
mNextCsdWarning = parseGlobalSettingFloat(
Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING, /* defaultValue= */1.f);
final List<SoundDoseRecord> records = persistedStringToRecordList(
mSettings.getGlobalString(mAudioService.getContentResolver(),
Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS),
mGlobalTimeOffsetInSecs);
if (records != null) {
mDoseRecords.addAll(records);
}
}
}
reset();
}
private void onConfigureSafeMedia(boolean force, String caller) {
synchronized (mSafeMediaVolumeStateLock) {
int mcc = mContext.getResources().getConfiguration().mcc;
@@ -668,10 +754,6 @@ public class SoundDoseHelper {
/*obj=*/null), /*delay=*/0);
}
}
if (mEnableCsd) {
initCsd();
}
}
private int getTimeoutMsForWarning(@AudioManager.CsdWarning int csdWarning) {