From e8924115f9a57d35149da89c2a1bd920fdd0fe39 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Thu, 3 Mar 2016 16:53:16 -0800 Subject: [PATCH] Dynamic audio policies: allow device passing for RENDER mixes AudioMix: add system method for specifying an AudioDeviceInfo to be used by this mix. This only works for AudioMix instance with the RENDER route flag. Previous dynamic policy implementation didn't enforce mix route flag check, but only supported LOOP_BACK, so make LOOP_BACK the default. When a policy gets registered and the registration ID is set on each mix, for RENDER mixes use the device address for registration. Bug 25448664 Change-Id: If5789d84ff4c4c25a6e81ba1513a39916220498a --- api/system-current.txt | 1 + .../android/media/audiopolicy/AudioMix.java | 93 +++++++++++++++++-- .../media/audiopolicy/AudioPolicyConfig.java | 17 +++- 3 files changed, 100 insertions(+), 11 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index 3b8f584c30067..7ae88693d4ed3 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -23826,6 +23826,7 @@ package android.media.audiopolicy { public static class AudioMix.Builder { ctor public AudioMix.Builder(android.media.audiopolicy.AudioMixingRule) throws java.lang.IllegalArgumentException; method public android.media.audiopolicy.AudioMix build() throws java.lang.IllegalArgumentException; + method public android.media.audiopolicy.AudioMix.Builder setDevice(android.media.AudioDeviceInfo) throws java.lang.IllegalArgumentException; method public android.media.audiopolicy.AudioMix.Builder setFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException; method public android.media.audiopolicy.AudioMix.Builder setRouteFlags(int) throws java.lang.IllegalArgumentException; } diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java index 55fb82bbdcc53..56d3c9987bc12 100644 --- a/media/java/android/media/audiopolicy/AudioMix.java +++ b/media/java/android/media/audiopolicy/AudioMix.java @@ -17,7 +17,9 @@ package android.media.audiopolicy; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.SystemApi; +import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioSystem; @@ -36,19 +38,28 @@ public class AudioMix { private int mRouteFlags; private String mRegistrationId; private int mMixType = MIX_TYPE_INVALID; + + // written by AudioPolicy int mMixState = MIX_STATE_DISABLED; int mCallbackFlags; + // initialized in constructor, read by AudioPolicyConfig + final int mDeviceId; + final String mDeviceAddress; + /** * All parameters are guaranteed valid through the Builder. */ - private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags) { + private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags, + int deviceId, String deviceAddress) { mRule = rule; mFormat = format; mRouteFlags = routeFlags; mRegistrationId = null; mMixType = rule.getTargetMixType(); mCallbackFlags = callbackFlags; + mDeviceId = deviceId; + mDeviceAddress = deviceAddress; } // CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined @@ -74,6 +85,8 @@ public class AudioMix { @SystemApi public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1; + private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK; + // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h /** * @hide @@ -172,6 +185,8 @@ public class AudioMix { private AudioFormat mFormat = null; private int mRouteFlags = 0; private int mCallbackFlags = 0; + private int mDeviceId = -1; + private String mDeviceAddress = null; /** * @hide @@ -200,7 +215,7 @@ public class AudioMix { * @return the same Builder instance. * @throws IllegalArgumentException */ - public Builder setMixingRule(AudioMixingRule rule) + Builder setMixingRule(AudioMixingRule rule) throws IllegalArgumentException { if (rule == null) { throw new IllegalArgumentException("Illegal null AudioMixingRule argument"); @@ -216,7 +231,7 @@ public class AudioMix { * @return the same Builder instance. * @throws IllegalArgumentException */ - public Builder setCallbackFlags(int flags) throws IllegalArgumentException { + Builder setCallbackFlags(int flags) throws IllegalArgumentException { if ((flags != 0) && ((flags & CALLBACK_FLAGS_ALL) == 0)) { throw new IllegalArgumentException("Illegal callback flags 0x" + Integer.toHexString(flags).toUpperCase()); @@ -225,6 +240,19 @@ public class AudioMix { return this; } + /** + * @hide + * Only used by AudioPolicyConfig, not a public API. + * @param deviceId + * @param address + * @return the same Builder instance. + */ + Builder setDevice(int deviceId, String address) { + mDeviceId = deviceId; + mDeviceAddress = address; + return this; + } + /** * Sets the {@link AudioFormat} for the mix. * @param format a non-null {@link AudioFormat} instance. @@ -242,7 +270,8 @@ public class AudioMix { } /** - * Sets the routing behavior for the mix. + * Sets the routing behavior for the mix. If not set, routing behavior will default to + * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}. * @param routeFlags one of {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, * {@link AudioMix#ROUTE_FLAG_RENDER} * @return the same Builder instance. @@ -254,14 +283,40 @@ public class AudioMix { if (routeFlags == 0) { throw new IllegalArgumentException("Illegal empty route flags"); } - if ((routeFlags & (ROUTE_FLAG_LOOP_BACK | ROUTE_FLAG_RENDER)) == 0) { + if ((routeFlags & ROUTE_FLAG_SUPPORTED) == 0) { throw new IllegalArgumentException("Invalid route flags 0x" - + Integer.toHexString(routeFlags) + "when creating an AudioMix"); + + Integer.toHexString(routeFlags) + "when configuring an AudioMix"); + } + if ((routeFlags & ~ROUTE_FLAG_SUPPORTED) != 0) { + throw new IllegalArgumentException("Unknown route flags 0x" + + Integer.toHexString(routeFlags) + "when configuring an AudioMix"); } mRouteFlags = routeFlags; return this; } + /** + * Sets the audio device used for playback. Cannot be used in the context of an audio + * policy used to inject audio to be recorded, or in a mix whose route flags doesn't + * specify {@link AudioMix#ROUTE_FLAG_RENDER}. + * @param device a non-null AudioDeviceInfo describing the audio device to play the output + * of this mix. + * @return the same Builder instance + * @throws IllegalArgumentException + */ + @SystemApi + public Builder setDevice(@NonNull AudioDeviceInfo device) throws IllegalArgumentException { + if (device == null) { + throw new IllegalArgumentException("Illegal null AudioDeviceInfo argument"); + } + if (!device.isSink()) { + throw new IllegalArgumentException("Unsupported device type on mix, not a sink"); + } + mDeviceId = device.getId(); + mDeviceAddress = device.getAddress(); + return this; + } + /** * Combines all of the settings and return a new {@link AudioMix} object. * @return a new {@link AudioMix} object @@ -273,8 +328,13 @@ public class AudioMix { throw new IllegalArgumentException("Illegal null AudioMixingRule"); } if (mRouteFlags == 0) { - // no route flags set, use default - mRouteFlags = ROUTE_FLAG_RENDER; + // no route flags set, use default as described in Builder.setRouteFlags(int) + mRouteFlags = ROUTE_FLAG_LOOP_BACK; + } + // can't do loop back AND render at same time in this implementation + if (mRouteFlags == (ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK)) { + throw new IllegalArgumentException("Unsupported route behavior combination 0x" + + Integer.toHexString(mRouteFlags)); } if (mFormat == null) { // FIXME Can we eliminate this? Will AudioMix work with an unspecified sample rate? @@ -284,7 +344,22 @@ public class AudioMix { } mFormat = new AudioFormat.Builder().setSampleRate(rate).build(); } - return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags); + if (mDeviceId != -1) { + if ((mRouteFlags & ROUTE_FLAG_RENDER) == 0) { + throw new IllegalArgumentException( + "Can't have audio device without flag ROUTE_FLAG_RENDER"); + } + if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) { + throw new IllegalArgumentException("Unsupported device on non-playback mix"); + } + } else { + if ((mRouteFlags & ROUTE_FLAG_RENDER) == ROUTE_FLAG_RENDER) { + throw new IllegalArgumentException( + "Can't have flag ROUTE_FLAG_RENDER without an audio device"); + } + } + return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceId, + mDeviceAddress); } } } diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java index 5d2bac02a5e04..3af3ae71a734c 100644 --- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java +++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java @@ -17,6 +17,8 @@ package android.media.audiopolicy; import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioPatch; import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion; import android.os.Parcel; import android.os.Parcelable; @@ -81,6 +83,9 @@ public class AudioPolicyConfig implements Parcelable { dest.writeInt(mix.getRouteFlags()); // write callback flags dest.writeInt(mix.mCallbackFlags); + // write device information + dest.writeInt(mix.mDeviceId); + dest.writeString(mix.mDeviceAddress); // write mix format dest.writeInt(mix.getFormat().getSampleRate()); dest.writeInt(mix.getFormat().getEncoding()); @@ -104,6 +109,8 @@ public class AudioPolicyConfig implements Parcelable { mixBuilder.setRouteFlags(routeFlags); // read callback flags mixBuilder.setCallbackFlags(in.readInt()); + // read device information + mixBuilder.setDevice(in.readInt(), in.readString()); // read mix format int sampleRate = in.readInt(); int encoding = in.readInt(); @@ -197,8 +204,14 @@ public class AudioPolicyConfig implements Parcelable { int mixIndex = 0; for (AudioMix mix : mMixes) { if (!mRegistrationId.isEmpty()) { - mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":" - + mixIndex++); + if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) == + AudioMix.ROUTE_FLAG_LOOP_BACK) { + mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":" + + mixIndex++); + } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) == + AudioMix.ROUTE_FLAG_RENDER) { + mix.setRegistration(mix.mDeviceAddress); + } } else { mix.setRegistration(""); }