/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media; import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE; import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION; import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL; import android.annotation.CallbackExecutor; import android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener; import android.os.Bundle; import android.os.PersistableBundle; import android.os.RemoteException; import android.util.Log; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; /** * Class used to handle the loudness related communication with the audio service. * * @hide */ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { private static final String TAG = "LoudnessCodecDispatcher"; private static final boolean DEBUG = false; private static final class LoudnessCodecUpdatesDispatcherStub extends ILoudnessCodecUpdatesDispatcher.Stub { private static LoudnessCodecUpdatesDispatcherStub sLoudnessCodecStub; private final CallbackUtil.LazyListenerManager mLoudnessListenerMgr = new CallbackUtil.LazyListenerManager<>(); private final Object mLock = new Object(); @GuardedBy("mLock") private final HashMap mConfiguratorListener = new HashMap<>(); public static synchronized LoudnessCodecUpdatesDispatcherStub getInstance() { if (sLoudnessCodecStub == null) { sLoudnessCodecStub = new LoudnessCodecUpdatesDispatcherStub(); } return sLoudnessCodecStub; } private LoudnessCodecUpdatesDispatcherStub() {} @Override public void dispatchLoudnessCodecParameterChange(int sessionId, PersistableBundle params) { if (DEBUG) { Log.d(TAG, "dispatchLoudnessCodecParameterChange for sessionId " + sessionId + " persistable bundle: " + params); } mLoudnessListenerMgr.callListeners(listener -> { synchronized (mLock) { mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> { // send the appropriate bundle for the user to update if (lcConfig.getSessionId() == sessionId) { lcConfig.mediaCodecsConsume(mcEntry -> { final LoudnessCodecInfo codecInfo = mcEntry.getKey(); final String infoKey = Integer.toString(codecInfo.hashCode()); Bundle bundle = null; if (params.containsKey(infoKey)) { bundle = new Bundle(params.getPersistableBundle(infoKey)); } final Set mediaCodecs = mcEntry.getValue(); for (MediaCodec mediaCodec : mediaCodecs) { final String mediaCodecKey = Integer.toString( mediaCodec.hashCode()); if (bundle == null && !params.containsKey(mediaCodecKey)) { continue; } boolean canBreak = false; if (bundle == null) { // key was set by media codec hash to update single codec bundle = new Bundle( params.getPersistableBundle(mediaCodecKey)); canBreak = true; } bundle = LoudnessCodecUpdatesDispatcherStub.filterLoudnessParams( l.onLoudnessCodecUpdate(mediaCodec, bundle)); if (!bundle.isDefinitelyEmpty()) { try { mediaCodec.setParameters(bundle); } catch (IllegalStateException e) { Log.w(TAG, "Cannot set loudness bundle on media codec " + mediaCodec); } } if (canBreak) { break; } } }); } return lcConfig; }); } }); } private static Bundle filterLoudnessParams(Bundle bundle) { Bundle filteredBundle = new Bundle(); if (bundle.containsKey(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)) { filteredBundle.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, bundle.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL)); } if (bundle.containsKey(KEY_AAC_DRC_HEAVY_COMPRESSION)) { filteredBundle.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, bundle.getInt(KEY_AAC_DRC_HEAVY_COMPRESSION)); } if (bundle.containsKey(KEY_AAC_DRC_EFFECT_TYPE)) { filteredBundle.putInt(KEY_AAC_DRC_EFFECT_TYPE, bundle.getInt(KEY_AAC_DRC_EFFECT_TYPE)); } return filteredBundle; } void addLoudnessCodecListener(@NonNull CallbackUtil.DispatcherStub dispatcher, @NonNull LoudnessCodecController configurator, @NonNull @CallbackExecutor Executor executor, @NonNull OnLoudnessCodecUpdateListener listener) { Objects.requireNonNull(configurator); Objects.requireNonNull(executor); Objects.requireNonNull(listener); mLoudnessListenerMgr.addListener( executor, listener, "addLoudnessCodecListener", () -> dispatcher); synchronized (mLock) { mConfiguratorListener.put(listener, configurator); } } void removeLoudnessCodecListener(@NonNull LoudnessCodecController configurator) { Objects.requireNonNull(configurator); OnLoudnessCodecUpdateListener listenerToRemove = null; synchronized (mLock) { Iterator> iterator = mConfiguratorListener.entrySet().iterator(); while (iterator.hasNext()) { Entry e = iterator.next(); if (e.getValue() == configurator) { final OnLoudnessCodecUpdateListener listener = e.getKey(); iterator.remove(); listenerToRemove = listener; break; } } } if (listenerToRemove != null) { mLoudnessListenerMgr.removeListener(listenerToRemove, "removeLoudnessCodecListener"); } } } @NonNull private final IAudioService mAudioService; /** @hide */ public LoudnessCodecDispatcher(@NonNull IAudioService audioService) { mAudioService = Objects.requireNonNull(audioService); } @Override public void register(boolean register) { try { if (register) { mAudioService.registerLoudnessCodecUpdatesDispatcher( LoudnessCodecUpdatesDispatcherStub.getInstance()); } else { mAudioService.unregisterLoudnessCodecUpdatesDispatcher( LoudnessCodecUpdatesDispatcherStub.getInstance()); } } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** @hide */ public void addLoudnessCodecListener(@NonNull LoudnessCodecController configurator, @NonNull @CallbackExecutor Executor executor, @NonNull OnLoudnessCodecUpdateListener listener) { LoudnessCodecUpdatesDispatcherStub.getInstance().addLoudnessCodecListener(this, configurator, executor, listener); } /** @hide */ public void removeLoudnessCodecListener(@NonNull LoudnessCodecController configurator) { LoudnessCodecUpdatesDispatcherStub.getInstance().removeLoudnessCodecListener(configurator); } /** @hide */ public void startLoudnessCodecUpdates(int sessionId) { try { mAudioService.startLoudnessCodecUpdates(sessionId); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** @hide */ public void stopLoudnessCodecUpdates(int sessionId) { try { mAudioService.stopLoudnessCodecUpdates(sessionId); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** @hide */ public void addLoudnessCodecInfo(int sessionId, int mediaCodecHash, @NonNull LoudnessCodecInfo mcInfo) { try { mAudioService.addLoudnessCodecInfo(sessionId, mediaCodecHash, mcInfo); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** @hide */ public void removeLoudnessCodecInfo(int sessionId, @NonNull LoudnessCodecInfo mcInfo) { try { mAudioService.removeLoudnessCodecInfo(sessionId, mcInfo); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** @hide */ public Bundle getLoudnessCodecParams(@NonNull LoudnessCodecInfo mcInfo) { Bundle loudnessParams = null; try { loudnessParams = new Bundle(mAudioService.getLoudnessParams(mcInfo)); } catch (RemoteException e) { e.rethrowFromSystemServer(); } return loudnessParams; } }