| /* |
| * 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<OnLoudnessCodecUpdateListener> |
| mLoudnessListenerMgr = new CallbackUtil.LazyListenerManager<>(); |
| |
| private final Object mLock = new Object(); |
| |
| @GuardedBy("mLock") |
| private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecController> |
| 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<MediaCodec> 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<Entry<OnLoudnessCodecUpdateListener, LoudnessCodecController>> iterator = |
| mConfiguratorListener.entrySet().iterator(); |
| while (iterator.hasNext()) { |
| Entry<OnLoudnessCodecUpdateListener, LoudnessCodecController> 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; |
| } |
| } |