Loading media/TEST_MAPPING +13 −1 Original line number Diff line number Diff line Loading @@ -49,6 +49,18 @@ {"exclude-annotation": "org.junit.Ignore"} ] } ], "postsubmit": [ { "file_patterns": [ "[^/]*(LoudnessCodec)[^/]*\\.java" ], "name": "LoudnessCodecApiTest", "options": [ { "include-annotation": "android.platform.test.annotations.Presubmit" } ] } ] } media/java/android/media/AudioManager.java +0 −29 Original line number Diff line number Diff line Loading @@ -19,8 +19,6 @@ 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; Loading Loading @@ -2923,33 +2921,6 @@ 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 /** Loading media/java/android/media/AudioSystem.java +5 −0 Original line number Diff line number Diff line Loading @@ -2462,6 +2462,8 @@ public class AudioSystem public static final int PLATFORM_VOICE = 1; /** @hide The platform is a television or a set-top box */ public static final int PLATFORM_TELEVISION = 2; /** @hide The platform is automotive */ public static final int PLATFORM_AUTOMOTIVE = 3; /** * @hide Loading @@ -2478,6 +2480,9 @@ public class AudioSystem return PLATFORM_VOICE; } else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { return PLATFORM_TELEVISION; } else if (context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_AUTOMOTIVE)) { return PLATFORM_AUTOMOTIVE; } else { return PLATFORM_DEFAULT; } Loading media/java/android/media/IAudioService.aidl +6 −8 Original line number Diff line number Diff line Loading @@ -52,7 +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.LoudnessCodecInfo; import android.media.PlayerBase; import android.media.VolumeInfo; import android.media.VolumePolicy; Loading Loading @@ -731,15 +731,13 @@ interface IAudioService { void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher); oneway void startLoudnessCodecUpdates(in int piid); oneway void startLoudnessCodecUpdates(int piid, in List<LoudnessCodecInfo> codecInfoSet); oneway void stopLoudnessCodecUpdates(in int piid); oneway void stopLoudnessCodecUpdates(int piid); oneway void addLoudnesssCodecFormat(in int piid, in LoudnessCodecFormat format); oneway void addLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo); oneway void addLoudnesssCodecFormatList(in int piid, in List<LoudnessCodecFormat> format); oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo); oneway void removeLoudnessCodecFormat(in int piid, in LoudnessCodecFormat format); PersistableBundle getLoudnessParams(in int piid, in LoudnessCodecFormat format); PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo); } media/java/android/media/LoudnessCodecConfigurator.java +284 −73 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.media; import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID; import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4; import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D; import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API; import android.annotation.CallbackExecutor; Loading @@ -23,21 +26,27 @@ import android.annotation.FlaggedApi; import android.os.Bundle; import android.util.Log; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; /** * 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. * parameter 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()}. * of {@link #create()} or {@link #create(Executor, OnLoudnessCodecUpdateListener)}. * * TODO: remove hide once API is final * @hide Loading Loading @@ -81,120 +90,255 @@ public class LoudnessCodecConfigurator { @NonNull private final LoudnessCodecDispatcher mLcDispatcher; private final Object mConfiguratorLock = new Object(); @GuardedBy("mConfiguratorLock") private AudioTrack mAudioTrack; private final List<MediaCodec> mMediaCodecs = new ArrayList<>(); @GuardedBy("mConfiguratorLock") private final Executor mExecutor; /** @hide */ protected LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher) { mLcDispatcher = Objects.requireNonNull(lcDispatcher); } @GuardedBy("mConfiguratorLock") private final OnLoudnessCodecUpdateListener mListener; @GuardedBy("mConfiguratorLock") private final HashMap<LoudnessCodecInfo, Set<MediaCodec>> mMediaCodecs = new HashMap<>(); /** * Creates a new instance of {@link LoudnessCodecConfigurator} * * <p>This method should be used when the client does not need to alter the * codec loudness parameters before they are applied to the audio decoders. * Otherwise, use {@link #create(Executor, OnLoudnessCodecUpdateListener)}. * * @return the {@link LoudnessCodecConfigurator} instance * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public static @NonNull LoudnessCodecConfigurator create() { return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()), Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {}); } /** * 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()}. * Creates a new instance of {@link LoudnessCodecConfigurator} * * <p>This method should be used when the client wants to alter the codec * loudness parameters before they are applied to the audio decoders. * Otherwise, use {@link #create()}. * * @param executor {@link Executor} to handle the callbacks * @param listener used to receive updates * @param listener used for receiving updates * * @return {@code true} if there is at least one {@link MediaCodec} and * {@link AudioTrack} set and the user can expect receiving updates. * @return the {@link LoudnessCodecConfigurator} instance * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public boolean startLoudnessCodecUpdates(@NonNull @CallbackExecutor Executor executor, public static @NonNull LoudnessCodecConfigurator create( @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); Objects.requireNonNull(executor, "Executor cannot be null"); Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null"); return checkStartLoudnessConfigurator(); return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()), executor, listener); } /** * Starts receiving asynchronous loudness updates. * <p>The registered MediaCodecs will be updated automatically without any client * callbacks. * Creates a new instance of {@link LoudnessCodecConfigurator} * * @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. * <p>This method should be used only in testing * * @param service interface for communicating with AudioService * @param executor {@link Executor} to handle the callbacks * @param listener used for receiving updates * * @return the {@link LoudnessCodecConfigurator} instance * * 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(); public static @NonNull LoudnessCodecConfigurator createForTesting( @NonNull IAudioService service, @NonNull @CallbackExecutor Executor executor, @NonNull OnLoudnessCodecUpdateListener listener) { Objects.requireNonNull(service, "IAudioService cannot be null"); Objects.requireNonNull(executor, "Executor cannot be null"); Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null"); return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(service), executor, listener); } /** @hide */ private LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher, @NonNull @CallbackExecutor Executor executor, @NonNull OnLoudnessCodecUpdateListener listener) { mLcDispatcher = Objects.requireNonNull(lcDispatcher, "Dispatcher cannot be null"); mExecutor = Objects.requireNonNull(executor, "Executor cannot be null"); mListener = Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null"); } /** * Stops receiving asynchronous loudness updates. * Sets the {@link AudioTrack} and starts receiving asynchronous updates for * the registered {@link MediaCodec}s (see {@link #addMediaCodec(MediaCodec)}) * * <p>The AudioTrack should be the one that receives audio data from the * added audio decoders and is used to determine the device routing on which * the audio streaming will take place. This will directly influence the * loudness parameters. * <p>After calling this method the framework will compute the initial set of * parameters which will be applied to the registered codecs/returned to the * listener for modification. * * @param audioTrack the track that will receive audio data from the provided * audio decoders. In case this is {@code null} this * method will have the effect of clearing the existing set * {@link AudioTrack} and will stop receiving asynchronous * loudness updates * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public void stopLoudnessCodecUpdates() { public void setAudioTrack(AudioTrack audioTrack) { List<LoudnessCodecInfo> codecInfos; int piid = PLAYER_PIID_INVALID; int oldPiid = PLAYER_PIID_INVALID; synchronized (mConfiguratorLock) { if (mAudioTrack != null && mAudioTrack == audioTrack) { Log.v(TAG, "Loudness configurator already started for piid: " + mAudioTrack.getPlayerIId()); return; } codecInfos = getLoudnessCodecInfoList_l(); if (mAudioTrack != null) { oldPiid = mAudioTrack.getPlayerIId(); mLcDispatcher.removeLoudnessCodecListener(this); } if (audioTrack != null) { piid = audioTrack.getPlayerIId(); mLcDispatcher.addLoudnessCodecListener(this, mExecutor, mListener); } mAudioTrack = audioTrack; } if (oldPiid != PLAYER_PIID_INVALID) { Log.v(TAG, "Loudness configurator stopping updates for piid: " + oldPiid); mLcDispatcher.stopLoudnessCodecUpdates(oldPiid); } if (piid != PLAYER_PIID_INVALID) { Log.v(TAG, "Loudness configurator starting updates for piid: " + piid); mLcDispatcher.startLoudnessCodecUpdates(piid, codecInfos); } } /** * Adds a new {@link MediaCodec} that will stream data to an {@link AudioTrack} * which is registered through {@link #setAudioTrack(AudioTrack)}. * which the client sets * (see {@link LoudnessCodecConfigurator#setAudioTrack(AudioTrack)}). * * <p>This method can be called while asynchronous updates are live. * * <p>No new element will be added if the passed {@code mediaCodec} was * previously added. * * @param mediaCodec the codec to start receiving asynchronous loudness * updates * * 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")); final MediaCodec mc = Objects.requireNonNull(mediaCodec, "MediaCodec for addMediaCodec cannot be null"); int piid = PLAYER_PIID_INVALID; final LoudnessCodecInfo mcInfo = getCodecInfo(mc); if (mcInfo != null) { synchronized (mConfiguratorLock) { final AtomicBoolean containsCodec = new AtomicBoolean(false); Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> { containsCodec.set(!codecSet.add(mc)); return codecSet; }); if (newSet == null) { newSet = new HashSet<>(); newSet.add(mc); mMediaCodecs.put(mcInfo, newSet); } if (containsCodec.get()) { Log.v(TAG, "Loudness configurator already added media codec " + mediaCodec); return; } if (mAudioTrack != null) { piid = mAudioTrack.getPlayerIId(); } } if (piid != PLAYER_PIID_INVALID) { mLcDispatcher.addLoudnessCodecInfo(piid, mcInfo); } } } /** * Removes the {@link MediaCodec} from receiving loudness updates. * * <p>This method can be called while asynchronous updates are live. * * <p>No elements will be removed if the passed mediaCodec was not added before. * * @param mediaCodec the element to remove for receiving asynchronous 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")); int piid = PLAYER_PIID_INVALID; LoudnessCodecInfo mcInfo; AtomicBoolean removed = new AtomicBoolean(false); mcInfo = getCodecInfo(Objects.requireNonNull(mediaCodec, "MediaCodec for removeMediaCodec cannot be null")); if (mcInfo != null) { synchronized (mConfiguratorLock) { if (mAudioTrack != null) { piid = mAudioTrack.getPlayerIId(); } mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> { removed.set(mcs.remove(mediaCodec)); if (mcs.isEmpty()) { // remove the entry return null; } return mcs; }); } /** * 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"); if (piid != PLAYER_PIID_INVALID && removed.get()) { mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo); } } } /** * 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. * Gets synchronous loudness updates when no listener is required. The provided * {@link MediaCodec} streams audio data to the passed {@link AudioTrack}. * * @param audioTrack track that receives audio data from the passed * {@link MediaCodec} * @param mediaCodec codec that decodes loudness annotated data for the passed * {@link AudioTrack} * * @return the {@link Bundle} containing the current loudness parameters. Caller is * responsible to update the {@link MediaCodec} Loading @@ -204,22 +348,89 @@ public class LoudnessCodecConfigurator { */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) @NonNull public Bundle getLoudnessCodecParams(@NonNull MediaCodec mediaCodec) { // TODO: implement synchronous loudness params updates public Bundle getLoudnessCodecParams(@NonNull AudioTrack audioTrack, @NonNull MediaCodec mediaCodec) { Objects.requireNonNull(audioTrack, "Passed audio track cannot be null"); LoudnessCodecInfo codecInfo = getCodecInfo(mediaCodec); if (codecInfo == null) { return new Bundle(); } private boolean checkStartLoudnessConfigurator() { return mLcDispatcher.getLoudnessCodecParams(audioTrack.getPlayerIId(), codecInfo); } /** @hide */ /*package*/ int getAssignedTrackPiid() { int piid = PLAYER_PIID_INVALID; synchronized (mConfiguratorLock) { if (mAudioTrack == null) { Log.w(TAG, "Cannot start loudness configurator without an AudioTrack"); return false; return piid; } piid = mAudioTrack.getPlayerIId(); } if (mMediaCodecs.isEmpty()) { Log.w(TAG, "Cannot start loudness configurator without at least one MediaCodec"); return false; return piid; } return true; /** @hide */ /*package*/ List<MediaCodec> getRegisteredMediaCodecList() { synchronized (mConfiguratorLock) { return mMediaCodecs.values().stream().flatMap(Collection::stream).toList(); } } @GuardedBy("mConfiguratorLock") private List<LoudnessCodecInfo> getLoudnessCodecInfoList_l() { return mMediaCodecs.values().stream().flatMap(listMc -> listMc.stream().map( LoudnessCodecConfigurator::getCodecInfo)).toList(); } @Nullable private static LoudnessCodecInfo getCodecInfo(@NonNull MediaCodec mediaCodec) { LoudnessCodecInfo lci = new LoudnessCodecInfo(); final MediaCodecInfo codecInfo = mediaCodec.getCodecInfo(); if (codecInfo.isEncoder()) { // loudness info only for decoders Log.w(TAG, "MediaCodec used for encoding does not support loudness annotation"); return null; } final MediaFormat inputFormat = mediaCodec.getInputFormat(); final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME); if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) { // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize one of // these two keys int aacProfile = -1; int profile = -1; try { aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE); } catch (NullPointerException e) { // does not contain KEY_AAC_PROFILE. do nothing } try { profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE); } catch (NullPointerException e) { // does not contain KEY_PROFILE. do nothing } if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) { lci.metadataType = CODEC_METADATA_TYPE_MPEG_D; } else { lci.metadataType = CODEC_METADATA_TYPE_MPEG_4; } } else { Log.w(TAG, "MediaCodec mime type not supported for loudness annotation"); return null; } final MediaFormat outputFormat = mediaCodec.getOutputFormat(); lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); lci.mediaCodecHashCode = mediaCodec.hashCode(); return lci; } } Loading
media/TEST_MAPPING +13 −1 Original line number Diff line number Diff line Loading @@ -49,6 +49,18 @@ {"exclude-annotation": "org.junit.Ignore"} ] } ], "postsubmit": [ { "file_patterns": [ "[^/]*(LoudnessCodec)[^/]*\\.java" ], "name": "LoudnessCodecApiTest", "options": [ { "include-annotation": "android.platform.test.annotations.Presubmit" } ] } ] }
media/java/android/media/AudioManager.java +0 −29 Original line number Diff line number Diff line Loading @@ -19,8 +19,6 @@ 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; Loading Loading @@ -2923,33 +2921,6 @@ 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 /** Loading
media/java/android/media/AudioSystem.java +5 −0 Original line number Diff line number Diff line Loading @@ -2462,6 +2462,8 @@ public class AudioSystem public static final int PLATFORM_VOICE = 1; /** @hide The platform is a television or a set-top box */ public static final int PLATFORM_TELEVISION = 2; /** @hide The platform is automotive */ public static final int PLATFORM_AUTOMOTIVE = 3; /** * @hide Loading @@ -2478,6 +2480,9 @@ public class AudioSystem return PLATFORM_VOICE; } else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { return PLATFORM_TELEVISION; } else if (context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_AUTOMOTIVE)) { return PLATFORM_AUTOMOTIVE; } else { return PLATFORM_DEFAULT; } Loading
media/java/android/media/IAudioService.aidl +6 −8 Original line number Diff line number Diff line Loading @@ -52,7 +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.LoudnessCodecInfo; import android.media.PlayerBase; import android.media.VolumeInfo; import android.media.VolumePolicy; Loading Loading @@ -731,15 +731,13 @@ interface IAudioService { void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher); oneway void startLoudnessCodecUpdates(in int piid); oneway void startLoudnessCodecUpdates(int piid, in List<LoudnessCodecInfo> codecInfoSet); oneway void stopLoudnessCodecUpdates(in int piid); oneway void stopLoudnessCodecUpdates(int piid); oneway void addLoudnesssCodecFormat(in int piid, in LoudnessCodecFormat format); oneway void addLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo); oneway void addLoudnesssCodecFormatList(in int piid, in List<LoudnessCodecFormat> format); oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo); oneway void removeLoudnessCodecFormat(in int piid, in LoudnessCodecFormat format); PersistableBundle getLoudnessParams(in int piid, in LoudnessCodecFormat format); PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo); }
media/java/android/media/LoudnessCodecConfigurator.java +284 −73 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.media; import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID; import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4; import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D; import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API; import android.annotation.CallbackExecutor; Loading @@ -23,21 +26,27 @@ import android.annotation.FlaggedApi; import android.os.Bundle; import android.util.Log; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; /** * 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. * parameter 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()}. * of {@link #create()} or {@link #create(Executor, OnLoudnessCodecUpdateListener)}. * * TODO: remove hide once API is final * @hide Loading Loading @@ -81,120 +90,255 @@ public class LoudnessCodecConfigurator { @NonNull private final LoudnessCodecDispatcher mLcDispatcher; private final Object mConfiguratorLock = new Object(); @GuardedBy("mConfiguratorLock") private AudioTrack mAudioTrack; private final List<MediaCodec> mMediaCodecs = new ArrayList<>(); @GuardedBy("mConfiguratorLock") private final Executor mExecutor; /** @hide */ protected LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher) { mLcDispatcher = Objects.requireNonNull(lcDispatcher); } @GuardedBy("mConfiguratorLock") private final OnLoudnessCodecUpdateListener mListener; @GuardedBy("mConfiguratorLock") private final HashMap<LoudnessCodecInfo, Set<MediaCodec>> mMediaCodecs = new HashMap<>(); /** * Creates a new instance of {@link LoudnessCodecConfigurator} * * <p>This method should be used when the client does not need to alter the * codec loudness parameters before they are applied to the audio decoders. * Otherwise, use {@link #create(Executor, OnLoudnessCodecUpdateListener)}. * * @return the {@link LoudnessCodecConfigurator} instance * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public static @NonNull LoudnessCodecConfigurator create() { return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()), Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {}); } /** * 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()}. * Creates a new instance of {@link LoudnessCodecConfigurator} * * <p>This method should be used when the client wants to alter the codec * loudness parameters before they are applied to the audio decoders. * Otherwise, use {@link #create()}. * * @param executor {@link Executor} to handle the callbacks * @param listener used to receive updates * @param listener used for receiving updates * * @return {@code true} if there is at least one {@link MediaCodec} and * {@link AudioTrack} set and the user can expect receiving updates. * @return the {@link LoudnessCodecConfigurator} instance * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public boolean startLoudnessCodecUpdates(@NonNull @CallbackExecutor Executor executor, public static @NonNull LoudnessCodecConfigurator create( @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); Objects.requireNonNull(executor, "Executor cannot be null"); Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null"); return checkStartLoudnessConfigurator(); return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()), executor, listener); } /** * Starts receiving asynchronous loudness updates. * <p>The registered MediaCodecs will be updated automatically without any client * callbacks. * Creates a new instance of {@link LoudnessCodecConfigurator} * * @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. * <p>This method should be used only in testing * * @param service interface for communicating with AudioService * @param executor {@link Executor} to handle the callbacks * @param listener used for receiving updates * * @return the {@link LoudnessCodecConfigurator} instance * * 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(); public static @NonNull LoudnessCodecConfigurator createForTesting( @NonNull IAudioService service, @NonNull @CallbackExecutor Executor executor, @NonNull OnLoudnessCodecUpdateListener listener) { Objects.requireNonNull(service, "IAudioService cannot be null"); Objects.requireNonNull(executor, "Executor cannot be null"); Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null"); return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(service), executor, listener); } /** @hide */ private LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher, @NonNull @CallbackExecutor Executor executor, @NonNull OnLoudnessCodecUpdateListener listener) { mLcDispatcher = Objects.requireNonNull(lcDispatcher, "Dispatcher cannot be null"); mExecutor = Objects.requireNonNull(executor, "Executor cannot be null"); mListener = Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null"); } /** * Stops receiving asynchronous loudness updates. * Sets the {@link AudioTrack} and starts receiving asynchronous updates for * the registered {@link MediaCodec}s (see {@link #addMediaCodec(MediaCodec)}) * * <p>The AudioTrack should be the one that receives audio data from the * added audio decoders and is used to determine the device routing on which * the audio streaming will take place. This will directly influence the * loudness parameters. * <p>After calling this method the framework will compute the initial set of * parameters which will be applied to the registered codecs/returned to the * listener for modification. * * @param audioTrack the track that will receive audio data from the provided * audio decoders. In case this is {@code null} this * method will have the effect of clearing the existing set * {@link AudioTrack} and will stop receiving asynchronous * loudness updates * * TODO: remove hide once API is final * @hide */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) public void stopLoudnessCodecUpdates() { public void setAudioTrack(AudioTrack audioTrack) { List<LoudnessCodecInfo> codecInfos; int piid = PLAYER_PIID_INVALID; int oldPiid = PLAYER_PIID_INVALID; synchronized (mConfiguratorLock) { if (mAudioTrack != null && mAudioTrack == audioTrack) { Log.v(TAG, "Loudness configurator already started for piid: " + mAudioTrack.getPlayerIId()); return; } codecInfos = getLoudnessCodecInfoList_l(); if (mAudioTrack != null) { oldPiid = mAudioTrack.getPlayerIId(); mLcDispatcher.removeLoudnessCodecListener(this); } if (audioTrack != null) { piid = audioTrack.getPlayerIId(); mLcDispatcher.addLoudnessCodecListener(this, mExecutor, mListener); } mAudioTrack = audioTrack; } if (oldPiid != PLAYER_PIID_INVALID) { Log.v(TAG, "Loudness configurator stopping updates for piid: " + oldPiid); mLcDispatcher.stopLoudnessCodecUpdates(oldPiid); } if (piid != PLAYER_PIID_INVALID) { Log.v(TAG, "Loudness configurator starting updates for piid: " + piid); mLcDispatcher.startLoudnessCodecUpdates(piid, codecInfos); } } /** * Adds a new {@link MediaCodec} that will stream data to an {@link AudioTrack} * which is registered through {@link #setAudioTrack(AudioTrack)}. * which the client sets * (see {@link LoudnessCodecConfigurator#setAudioTrack(AudioTrack)}). * * <p>This method can be called while asynchronous updates are live. * * <p>No new element will be added if the passed {@code mediaCodec} was * previously added. * * @param mediaCodec the codec to start receiving asynchronous loudness * updates * * 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")); final MediaCodec mc = Objects.requireNonNull(mediaCodec, "MediaCodec for addMediaCodec cannot be null"); int piid = PLAYER_PIID_INVALID; final LoudnessCodecInfo mcInfo = getCodecInfo(mc); if (mcInfo != null) { synchronized (mConfiguratorLock) { final AtomicBoolean containsCodec = new AtomicBoolean(false); Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> { containsCodec.set(!codecSet.add(mc)); return codecSet; }); if (newSet == null) { newSet = new HashSet<>(); newSet.add(mc); mMediaCodecs.put(mcInfo, newSet); } if (containsCodec.get()) { Log.v(TAG, "Loudness configurator already added media codec " + mediaCodec); return; } if (mAudioTrack != null) { piid = mAudioTrack.getPlayerIId(); } } if (piid != PLAYER_PIID_INVALID) { mLcDispatcher.addLoudnessCodecInfo(piid, mcInfo); } } } /** * Removes the {@link MediaCodec} from receiving loudness updates. * * <p>This method can be called while asynchronous updates are live. * * <p>No elements will be removed if the passed mediaCodec was not added before. * * @param mediaCodec the element to remove for receiving asynchronous 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")); int piid = PLAYER_PIID_INVALID; LoudnessCodecInfo mcInfo; AtomicBoolean removed = new AtomicBoolean(false); mcInfo = getCodecInfo(Objects.requireNonNull(mediaCodec, "MediaCodec for removeMediaCodec cannot be null")); if (mcInfo != null) { synchronized (mConfiguratorLock) { if (mAudioTrack != null) { piid = mAudioTrack.getPlayerIId(); } mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> { removed.set(mcs.remove(mediaCodec)); if (mcs.isEmpty()) { // remove the entry return null; } return mcs; }); } /** * 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"); if (piid != PLAYER_PIID_INVALID && removed.get()) { mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo); } } } /** * 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. * Gets synchronous loudness updates when no listener is required. The provided * {@link MediaCodec} streams audio data to the passed {@link AudioTrack}. * * @param audioTrack track that receives audio data from the passed * {@link MediaCodec} * @param mediaCodec codec that decodes loudness annotated data for the passed * {@link AudioTrack} * * @return the {@link Bundle} containing the current loudness parameters. Caller is * responsible to update the {@link MediaCodec} Loading @@ -204,22 +348,89 @@ public class LoudnessCodecConfigurator { */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) @NonNull public Bundle getLoudnessCodecParams(@NonNull MediaCodec mediaCodec) { // TODO: implement synchronous loudness params updates public Bundle getLoudnessCodecParams(@NonNull AudioTrack audioTrack, @NonNull MediaCodec mediaCodec) { Objects.requireNonNull(audioTrack, "Passed audio track cannot be null"); LoudnessCodecInfo codecInfo = getCodecInfo(mediaCodec); if (codecInfo == null) { return new Bundle(); } private boolean checkStartLoudnessConfigurator() { return mLcDispatcher.getLoudnessCodecParams(audioTrack.getPlayerIId(), codecInfo); } /** @hide */ /*package*/ int getAssignedTrackPiid() { int piid = PLAYER_PIID_INVALID; synchronized (mConfiguratorLock) { if (mAudioTrack == null) { Log.w(TAG, "Cannot start loudness configurator without an AudioTrack"); return false; return piid; } piid = mAudioTrack.getPlayerIId(); } if (mMediaCodecs.isEmpty()) { Log.w(TAG, "Cannot start loudness configurator without at least one MediaCodec"); return false; return piid; } return true; /** @hide */ /*package*/ List<MediaCodec> getRegisteredMediaCodecList() { synchronized (mConfiguratorLock) { return mMediaCodecs.values().stream().flatMap(Collection::stream).toList(); } } @GuardedBy("mConfiguratorLock") private List<LoudnessCodecInfo> getLoudnessCodecInfoList_l() { return mMediaCodecs.values().stream().flatMap(listMc -> listMc.stream().map( LoudnessCodecConfigurator::getCodecInfo)).toList(); } @Nullable private static LoudnessCodecInfo getCodecInfo(@NonNull MediaCodec mediaCodec) { LoudnessCodecInfo lci = new LoudnessCodecInfo(); final MediaCodecInfo codecInfo = mediaCodec.getCodecInfo(); if (codecInfo.isEncoder()) { // loudness info only for decoders Log.w(TAG, "MediaCodec used for encoding does not support loudness annotation"); return null; } final MediaFormat inputFormat = mediaCodec.getInputFormat(); final String mimeType = inputFormat.getString(MediaFormat.KEY_MIME); if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) { // check both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize one of // these two keys int aacProfile = -1; int profile = -1; try { aacProfile = inputFormat.getInteger(MediaFormat.KEY_AAC_PROFILE); } catch (NullPointerException e) { // does not contain KEY_AAC_PROFILE. do nothing } try { profile = inputFormat.getInteger(MediaFormat.KEY_PROFILE); } catch (NullPointerException e) { // does not contain KEY_PROFILE. do nothing } if (aacProfile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE || profile == MediaCodecInfo.CodecProfileLevel.AACObjectXHE) { lci.metadataType = CODEC_METADATA_TYPE_MPEG_D; } else { lci.metadataType = CODEC_METADATA_TYPE_MPEG_4; } } else { Log.w(TAG, "MediaCodec mime type not supported for loudness annotation"); return null; } final MediaFormat outputFormat = mediaCodec.getOutputFormat(); lci.isDownmixing = outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) < inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); lci.mediaCodecHashCode = mediaCodec.hashCode(); return lci; } }