Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e55af2dd authored by Vlad Popa's avatar Vlad Popa Committed by Android (Google) Code Review
Browse files

Merge "CTA2075: add client loudness configuration interfaces" into main

parents 79739385 cc485936
Loading
Loading
Loading
Loading
+28 −1
Original line number Diff line number Diff line
@@ -19,8 +19,8 @@ package android.media;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.content.Context.DEVICE_ID_DEFAULT;

import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;

import android.Manifest;
@@ -2941,6 +2941,33 @@ public class AudioManager {
        return new Spatializer(this);
    }

    //====================================================================
    // Loudness management
    private final Object mLoudnessCodecLock = new Object();

    @GuardedBy("mLoudnessCodecLock")
    private LoudnessCodecDispatcher mLoudnessCodecDispatcher = null;

    /**
     * Creates a new instance of {@link LoudnessCodecConfigurator}.
     * @return the {@link LoudnessCodecConfigurator} instance
     *
     * TODO: remove hide once API is final
     * @hide
     */
    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
    public @NonNull LoudnessCodecConfigurator createLoudnessCodecConfigurator() {
        LoudnessCodecConfigurator configurator;
        synchronized (mLoudnessCodecLock) {
            // initialize lazily
            if (mLoudnessCodecDispatcher == null) {
                mLoudnessCodecDispatcher = new LoudnessCodecDispatcher(this);
            }
            configurator = mLoudnessCodecDispatcher.createLoudnessCodecConfigurator();
        }
        return configurator;
    }

    //====================================================================
    // Bluetooth SCO control
    /**
+18 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
import android.media.IDeviceVolumeBehaviorDispatcher;
import android.media.IDevicesForAttributesCallback;
import android.media.ILoudnessCodecUpdatesDispatcher;
import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IPreferredMixerAttributesDispatcher;
@@ -51,6 +52,7 @@ import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerOutputCallback;
import android.media.IStreamAliasingDispatcher;
import android.media.IVolumeController;
import android.media.LoudnessCodecFormat;
import android.media.PlayerBase;
import android.media.VolumeInfo;
import android.media.VolumePolicy;
@@ -728,4 +730,20 @@ interface IAudioService {
    @EnforcePermission("MODIFY_AUDIO_ROUTING")
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
    boolean isBluetoothVariableLatencyEnabled();

    void registerLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher);

    void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher);

    oneway void startLoudnessCodecUpdates(in int piid);

    oneway void stopLoudnessCodecUpdates(in int piid);

    oneway void addLoudnesssCodecFormat(in int piid, in LoudnessCodecFormat format);

    oneway void addLoudnesssCodecFormatList(in int piid, in List<LoudnessCodecFormat> format);

    oneway void removeLoudnessCodecFormat(in int piid, in LoudnessCodecFormat format);

    PersistableBundle getLoudnessParams(in int piid, in LoudnessCodecFormat format);
}
+31 −0
Original line number Diff line number Diff line
/*
 * 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 android.os.PersistableBundle;

/**
 * Interface which provides updates for the clients about MediaCodec loudness
 * parameter changes.
 *
 * {@hide}
 */
oneway interface ILoudnessCodecUpdatesDispatcher {

    void dispatchLoudnessCodecParameterChange(int piid, in PersistableBundle params);

}
 No newline at end of file
+225 −0
Original line number Diff line number Diff line
/*
 * 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.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;

import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Class for getting recommended loudness parameter updates for audio decoders, according to the
 * encoded format and current audio routing. Those updates can be automatically applied to the
 * {@link MediaCodec} instance(s), or be provided to the user. The codec loudness management
 * updates are defined by the CTA-2075 standard.
 * <p>A new object should be instantiated for each {@link AudioTrack} with the help
 * of {@link AudioManager#createLoudnessCodecConfigurator()}.
 *
 * TODO: remove hide once API is final
 * @hide
 */
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
public class LoudnessCodecConfigurator {
    private static final String TAG = "LoudnessCodecConfigurator";

    /**
     * Listener used for receiving asynchronous loudness metadata updates.
     *
     * TODO: remove hide once API is final
     * @hide
     */
    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
    public interface OnLoudnessCodecUpdateListener {
        /**
         * Contains the MediaCodec key/values that can be set directly to
         * configure the loudness of the handle's corresponding decoder (see
         * {@link MediaCodec#setParameters(Bundle)}).
         *
         * @param mediaCodec  the mediaCodec that will receive the new parameters
         * @param codecValues contains loudness key/value pairs that can be set
         *                    directly on the mediaCodec. The listener can modify
         *                    these values with their own edits which will be
         *                    returned for the mediaCodec configuration
         * @return a Bundle which contains the original computed codecValues
         * aggregated with user edits. The platform will configure the associated
         * MediaCodecs with the returned Bundle params.
         *
         * TODO: remove hide once API is final
         * @hide
         */
        @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
        @NonNull
        default Bundle onLoudnessCodecUpdate(@NonNull MediaCodec mediaCodec,
                                             @NonNull Bundle codecValues) {
            return codecValues;
        }
    }

    @NonNull private final LoudnessCodecDispatcher mLcDispatcher;

    private AudioTrack mAudioTrack;

    private final List<MediaCodec> mMediaCodecs = new ArrayList<>();

    /** @hide */
    protected LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher) {
        mLcDispatcher = Objects.requireNonNull(lcDispatcher);
    }


    /**
     * Starts receiving asynchronous loudness updates and registers the listener for
     * receiving {@link MediaCodec} loudness parameter updates.
     * <p>This method should be called before {@link #startLoudnessCodecUpdates()} or
     * after {@link #stopLoudnessCodecUpdates()}.
     *
     * @param executor {@link Executor} to handle the callbacks
     * @param listener used to receive updates
     *
     * @return {@code true} if there is at least one {@link MediaCodec} and
     * {@link AudioTrack} set and the user can expect receiving updates.
     *
     * TODO: remove hide once API is final
     * @hide
     */
    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
    public boolean startLoudnessCodecUpdates(@NonNull @CallbackExecutor Executor executor,
                                             @NonNull OnLoudnessCodecUpdateListener listener) {
        Objects.requireNonNull(executor,
                "Executor must not be null");
        Objects.requireNonNull(listener,
                "OnLoudnessCodecUpdateListener must not be null");
        mLcDispatcher.addLoudnessCodecListener(this, executor, listener);

        return checkStartLoudnessConfigurator();
    }

    /**
     * Starts receiving asynchronous loudness updates.
     * <p>The registered MediaCodecs will be updated automatically without any client
     * callbacks.
     *
     * @return {@code true} if there is at least one MediaCodec and AudioTrack set
     * (see {@link #setAudioTrack(AudioTrack)}, {@link #addMediaCodec(MediaCodec)})
     * and the user can expect receiving updates.
     *
     * TODO: remove hide once API is final
     * @hide
     */
    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
    public boolean startLoudnessCodecUpdates() {
        mLcDispatcher.addLoudnessCodecListener(this,
                Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {});
        return checkStartLoudnessConfigurator();
    }

    /**
     * Stops receiving asynchronous loudness updates.
     *
     * TODO: remove hide once API is final
     * @hide
     */
    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
    public void stopLoudnessCodecUpdates() {
        mLcDispatcher.removeLoudnessCodecListener(this);
    }

    /**
     * Adds a new {@link MediaCodec} that will stream data to an {@link AudioTrack}
     * which is registered through {@link #setAudioTrack(AudioTrack)}.
     *
     * TODO: remove hide once API is final
     * @hide
     */
    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
    public void addMediaCodec(@NonNull MediaCodec mediaCodec) {
        mMediaCodecs.add(Objects.requireNonNull(mediaCodec,
                "MediaCodec for addMediaCodec must not be null"));
    }

    /**
     * Removes the {@link MediaCodec} from receiving loudness updates.
     *
     * TODO: remove hide once API is final
     * @hide
     */
    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
    public void removeMediaCodec(@NonNull MediaCodec mediaCodec) {
        mMediaCodecs.remove(Objects.requireNonNull(mediaCodec,
                "MediaCodec for removeMediaCodec must not be null"));
    }

    /**
     * Sets the {@link AudioTrack} that can receive audio data from the added
     * {@link MediaCodec}'s. The {@link AudioTrack} is used to determine the devices
     * on which the streaming will take place and hence will directly influence the
     * loudness params.
     * <p>Should be called before starting the loudness updates
     * (see {@link #startLoudnessCodecUpdates()},
     * {@link #startLoudnessCodecUpdates(Executor, OnLoudnessCodecUpdateListener)})
     *
     * TODO: remove hide once API is final
     * @hide
     */
    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
    public void setAudioTrack(@NonNull AudioTrack audioTrack) {
        mAudioTrack = Objects.requireNonNull(audioTrack,
                "AudioTrack for setAudioTrack must not be null");
    }

    /**
     * Gets synchronous loudness updates when no listener is required and at least one
     * {@link MediaCodec} which streams to a registered {@link AudioTrack} is set.
     * Otherwise, an empty {@link Bundle} will be returned.
     *
     * @return the {@link Bundle} containing the current loudness parameters. Caller is
     * responsible to update the {@link MediaCodec}
     *
     * TODO: remove hide once API is final
     * @hide
     */
    @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
    @NonNull
    public Bundle getLoudnessCodecParams(@NonNull MediaCodec mediaCodec) {
        // TODO: implement synchronous loudness params updates
        return new Bundle();
    }

    private boolean checkStartLoudnessConfigurator() {
        if (mAudioTrack == null) {
            Log.w(TAG, "Cannot start loudness configurator without an AudioTrack");
            return false;
        }

        if (mMediaCodecs.isEmpty()) {
            Log.w(TAG, "Cannot start loudness configurator without at least one MediaCodec");
            return false;
        }

        return true;
    }
}
+109 −0
Original line number Diff line number Diff line
/*
 * 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 android.annotation.CallbackExecutor;
import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener;
import android.os.PersistableBundle;
import android.os.RemoteException;

import androidx.annotation.NonNull;

import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * Class used to handle the loudness related communication with the audio service.
 * @hide
 */
public class LoudnessCodecDispatcher {
    private final class LoudnessCodecUpdatesDispatcherStub
            extends ILoudnessCodecUpdatesDispatcher.Stub
            implements CallbackUtil.DispatcherStub {
        @Override
        public void dispatchLoudnessCodecParameterChange(int piid, PersistableBundle params) {
            mLoudnessListenerMgr.callListeners(listener ->
                    mConfiguratorListener.computeIfPresent(listener, (l, c) -> {
                        // TODO: send the bundle for the user to update
                        return c;
                    }));
        }

        @Override
        public void register(boolean register) {
            try {
                if (register) {
                    mAm.getService().registerLoudnessCodecUpdatesDispatcher(this);
                } else {
                    mAm.getService().unregisterLoudnessCodecUpdatesDispatcher(this);
                }
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
        }
    }

    private final CallbackUtil.LazyListenerManager<OnLoudnessCodecUpdateListener>
            mLoudnessListenerMgr = new CallbackUtil.LazyListenerManager<>();

    @NonNull private final LoudnessCodecUpdatesDispatcherStub mLoudnessCodecStub;

    private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>
            mConfiguratorListener = new HashMap<>();

    @NonNull private final AudioManager mAm;

    protected LoudnessCodecDispatcher(@NonNull AudioManager am) {
        mAm = Objects.requireNonNull(am);
        mLoudnessCodecStub = new LoudnessCodecUpdatesDispatcherStub();
    }

    /** @hide */
    public LoudnessCodecConfigurator createLoudnessCodecConfigurator() {
        return new LoudnessCodecConfigurator(this);
    }

    /** @hide */
    public void addLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator,
                                         @NonNull @CallbackExecutor Executor executor,
                                         @NonNull OnLoudnessCodecUpdateListener listener) {
        Objects.requireNonNull(configurator);
        Objects.requireNonNull(executor);
        Objects.requireNonNull(listener);

        mConfiguratorListener.put(listener, configurator);
        mLoudnessListenerMgr.addListener(
                executor, listener, "addLoudnessCodecListener", () -> mLoudnessCodecStub);
    }

    /** @hide */
    public void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) {
        Objects.requireNonNull(configurator);

        for (Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e :
                mConfiguratorListener.entrySet()) {
            if (e.getValue() == configurator) {
                final OnLoudnessCodecUpdateListener listener = e.getKey();
                mConfiguratorListener.remove(listener);
                mLoudnessListenerMgr.removeListener(listener, "removeLoudnessCodecListener");
                break;
            }
        }
    }
}
Loading