From 0fea35bba8ed153d72f0f0e8528390ad8ab23c51 Mon Sep 17 00:00:00 2001 From: Vlad Popa Date: Wed, 1 Feb 2023 12:03:09 +0100 Subject: [PATCH] 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 --- .../android/server/audio/AudioService.java | 11 ++ .../android/server/audio/SoundDoseHelper.java | 172 +++++++++++++----- 2 files changed, 138 insertions(+), 45 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c2c59f2026ccd..be72aba012848 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -8350,6 +8350,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) { @@ -8358,6 +8359,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; @@ -8366,6 +8368,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); } } @@ -8840,6 +8847,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); diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 4e8e70420955d..4b302345eb08d 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -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 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 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 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 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) {