Loading media/java/android/media/AudioAttributes.java +2 −1 Original line number Diff line number Diff line Loading @@ -241,11 +241,11 @@ public final class AudioAttributes implements Parcelable { /** * @hide * CANDIDATE FOR PUBLIC API * Return the capture preset. * @return one of the values that can be set in {@link Builder#setCapturePreset(int)} or a * negative value if none has been set. */ @SystemApi public int getCapturePreset() { return mSource; } Loading Loading @@ -508,6 +508,7 @@ public final class AudioAttributes implements Parcelable { * {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}. * @return the same Builder instance. */ @SystemApi public Builder setCapturePreset(int preset) { switch (preset) { case MediaRecorder.AudioSource.DEFAULT: Loading media/java/android/media/AudioManager.java +8 −6 Original line number Diff line number Diff line Loading @@ -2459,6 +2459,7 @@ public class AudioManager { * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)} * @throws IllegalArgumentException */ @SystemApi public int requestAudioFocus(OnAudioFocusChangeListener l, @NonNull AudioAttributes requestAttributes, int durationHint, Loading Loading @@ -2853,17 +2854,17 @@ public class AudioManager { /** * @hide * CANDIDATE FOR PUBLIC API * Register the given {@link AudioPolicy}. * This call is synchronous and blocks until the registration process successfully completed * or failed to complete. * @param policy the {@link AudioPolicy} to register. * @param policy the non-null {@link AudioPolicy} to register. * @return {@link #ERROR} if there was an error communicating with the registration service * or if the user doesn't have the required * {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission, * {@link #SUCCESS} otherwise. */ public int registerAudioPolicy(AudioPolicy policy) { @SystemApi public int registerAudioPolicy(@NonNull AudioPolicy policy) { if (policy == null) { throw new IllegalArgumentException("Illegal null AudioPolicy argument"); } Loading @@ -2885,16 +2886,17 @@ public class AudioManager { /** * @hide * CANDIDATE FOR PUBLIC API * @param policy the {@link AudioPolicy} to unregister. * @param policy the non-null {@link AudioPolicy} to unregister. */ public void unregisterAudioPolicyAsync(AudioPolicy policy) { @SystemApi public void unregisterAudioPolicyAsync(@NonNull AudioPolicy policy) { if (policy == null) { throw new IllegalArgumentException("Illegal null AudioPolicy argument"); } IAudioService service = getService(); try { service.unregisterAudioPolicyAsync(policy.token()); policy.setRegistration(null); } catch (RemoteException e) { Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e); } Loading media/java/android/media/AudioService.java +23 −6 Original line number Diff line number Diff line Loading @@ -5538,6 +5538,8 @@ public class AudioService extends IAudioService.Stub { pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs); pw.print(" mMcc="); pw.println(mMcc); pw.print(" mHasVibrator="); pw.println(mHasVibrator); dumpAudioPolicies(pw); } private static String safeMediaVolumeStateToString(Integer state) { Loading Loading @@ -5797,6 +5799,10 @@ public class AudioService extends IAudioService.Stub { } synchronized (mAudioPolicies) { try { if (mAudioPolicies.containsKey(cb)) { Slog.e(TAG, "Cannot re-register policy"); return null; } AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb); cb.linkToDeath(app, 0/*flags*/); regId = app.connectMixes(); Loading @@ -5817,6 +5823,7 @@ public class AudioService extends IAudioService.Stub { if (app == null) { Slog.w(TAG, "Trying to unregister unknown audio policy for pid " + Binder.getCallingPid() + " / uid " + Binder.getCallingUid()); return; } else { cb.unlinkToDeath(app, 0/*flags*/); } Loading @@ -5825,12 +5832,21 @@ public class AudioService extends IAudioService.Stub { // TODO implement clearing mix attribute matching info in native audio policy } private void dumpAudioPolicies(PrintWriter pw) { pw.println("\nAudio policies:"); synchronized (mAudioPolicies) { for(AudioPolicyProxy policy : mAudioPolicies.values()) { pw.println(policy.toLogFriendlyString()); } } } //====================== // Audio policy proxy //====================== /** * This internal class inherits from AudioPolicyConfig which contains all the mixes and * their configurations. * This internal class inherits from AudioPolicyConfig, each instance contains all the * mixes of an AudioPolicy and their configurations. */ public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient { private static final String TAG = "AudioPolicyProxy"; Loading @@ -5838,7 +5854,7 @@ public class AudioService extends IAudioService.Stub { IBinder mToken; AudioPolicyProxy(AudioPolicyConfig config, IBinder token) { super(config); setRegistration(new String(config.toString() + ":ap:" + mAudioPolicyCounter++)); setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++)); mToken = token; } Loading @@ -5852,7 +5868,7 @@ public class AudioService extends IAudioService.Stub { String connectMixes() { updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE); return mRegistrationId; return getRegistration(); } void disconnectMixes() { Loading @@ -5863,8 +5879,9 @@ public class AudioService extends IAudioService.Stub { for (AudioMix mix : mMixes) { // TODO implement sending the mix attribute matching info to native audio policy if (DEBUG_AP) { Log.v(TAG, "AudioPolicyProxy connect mix state=" + connectionState + " addr=" + mix.getRegistration()); } Log.v(TAG, "AudioPolicyProxy mix new connection state=" + connectionState + " addr=" + mix.getRegistration()); } AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, connectionState, mix.getRegistration()); Loading media/java/android/media/audiopolicy/AudioMix.java +40 −1 Original line number Diff line number Diff line Loading @@ -17,21 +17,25 @@ package android.media.audiopolicy; import android.annotation.IntDef; import android.annotation.SystemApi; import android.media.AudioFormat; import android.media.AudioSystem; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** * @hide */ @SystemApi public class AudioMix { private AudioMixingRule mRule; private AudioFormat mFormat; private int mRouteFlags; private String mRegistrationId; private int mMixType = MIX_TYPE_INVALID; /** * All parameters are guaranteed valid through the Builder. Loading @@ -41,20 +45,39 @@ public class AudioMix { mFormat = format; mRouteFlags = routeFlags; mRegistrationId = null; mMixType = rule.getTargetMixType(); } /** * An audio mix behavior where the output of the mix is sent to the original destination of * the audio signal, i.e. an output device for an output mix, or a recording for an input mix. */ @SystemApi public static final int ROUTE_FLAG_RENDER = 0x1; /** * An audio mix behavior where the output of the mix is rerouted back to the framework and * is accessible for injection or capture through the {@link Audiotrack} and {@link AudioRecord} * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord} * APIs. */ @SystemApi public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1; /** * @hide * Invalid mix type, default value. */ public static final int MIX_TYPE_INVALID = -1; /** * @hide * Mix type indicating playback streams are mixed. */ public static final int MIX_TYPE_PLAYERS = 0; /** * @hide * Mix type indicating recording streams are mixed. */ public static final int MIX_TYPE_RECORDERS = 1; int getRouteFlags() { return mRouteFlags; } Loading @@ -67,6 +90,11 @@ public class AudioMix { return mRule; } /** @hide */ public int getMixType() { return mMixType; } void setRegistration(String regId) { mRegistrationId = regId; } Loading @@ -76,6 +104,12 @@ public class AudioMix { return mRegistrationId; } /** @hide */ @Override public int hashCode() { return Objects.hash(mRouteFlags, mRule, mMixType, mFormat); } /** @hide */ @IntDef(flag = true, value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } ) Loading @@ -86,6 +120,7 @@ public class AudioMix { * Builder class for {@link AudioMix} objects * */ @SystemApi public static class Builder { private AudioMixingRule mRule = null; private AudioFormat mFormat = null; Loading @@ -102,6 +137,7 @@ public class AudioMix { * @param rule a non-null {@link AudioMixingRule} instance. * @throws IllegalArgumentException */ @SystemApi public Builder(AudioMixingRule rule) throws IllegalArgumentException { if (rule == null) { Loading Loading @@ -132,6 +168,7 @@ public class AudioMix { * @return the same Builder instance. * @throws IllegalArgumentException */ @SystemApi public Builder setFormat(AudioFormat format) throws IllegalArgumentException { if (format == null) { Loading @@ -148,6 +185,7 @@ public class AudioMix { * @return the same Builder instance. * @throws IllegalArgumentException */ @SystemApi public Builder setRouteFlags(@RouteFlags int routeFlags) throws IllegalArgumentException { if (routeFlags == 0) { Loading @@ -166,6 +204,7 @@ public class AudioMix { * @return a new {@link AudioMix} object * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set. */ @SystemApi public AudioMix build() throws IllegalArgumentException { if (mRule == null) { throw new IllegalArgumentException("Illegal null AudioMixingRule"); Loading media/java/android/media/audiopolicy/AudioMixingRule.java +177 −10 Original line number Diff line number Diff line Loading @@ -16,10 +16,13 @@ package android.media.audiopolicy; import android.annotation.SystemApi; import android.media.AudioAttributes; import android.os.Parcel; import java.util.ArrayList; import java.util.Iterator; import java.util.Objects; /** Loading @@ -35,44 +38,114 @@ import java.util.Iterator; * .build(); * </pre> */ @SystemApi public class AudioMixingRule { private AudioMixingRule(ArrayList<AttributeMatchCriterion> criteria) { private AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria) { mCriteria = criteria; mTargetMixType = mixType; } /** * A rule requiring the usage information of the {@link AudioAttributes} to match * A rule requiring the usage information of the {@link AudioAttributes} to match. */ @SystemApi public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1; /** * A rule requiring the usage information of the {@link AudioAttributes} to differ * A rule requiring the capture preset information of the {@link AudioAttributes} to match. */ public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = 0x1 << 1; @SystemApi public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1; private final static int RULE_EXCLUSION_MASK = 0x8000; /** * @hide * A rule requiring the usage information of the {@link AudioAttributes} to differ. */ public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE; /** * @hide * A rule requiring the capture preset information of the {@link AudioAttributes} to differ. */ public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET = RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET; static final class AttributeMatchCriterion { AudioAttributes mAttr; int mRule; /** input parameters must be valid */ AttributeMatchCriterion(AudioAttributes attributes, int rule) { mAttr = attributes; mRule = rule; } @Override public int hashCode() { return Objects.hash(mAttr, mRule); } private ArrayList<AttributeMatchCriterion> mCriteria; void writeToParcel(Parcel dest) { dest.writeInt(mRule); if ((mRule == RULE_MATCH_ATTRIBUTE_USAGE) || (mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) { dest.writeInt(mAttr.getUsage()); } else { // capture preset rule dest.writeInt(mAttr.getCapturePreset()); } } } private final int mTargetMixType; int getTargetMixType() { return mTargetMixType; } private final ArrayList<AttributeMatchCriterion> mCriteria; ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; } @Override public int hashCode() { return Objects.hash(mTargetMixType, mCriteria); } private static boolean isValidSystemApiRule(int rule) { switch(rule) { case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: return true; default: return false; } } private static boolean isValidIntRule(int rule) { switch(rule) { case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_EXCLUDE_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: case RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET: return true; default: return false; } } private static boolean isPlayerRule(int rule) { return ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)); } /** * Builder class for {@link AudioMixingRule} objects * */ @SystemApi public static class Builder { private ArrayList<AttributeMatchCriterion> mCriteria; private int mTargetMixType = AudioMix.MIX_TYPE_INVALID; /** * Constructs a new Builder with no rules. */ @SystemApi public Builder() { mCriteria = new ArrayList<AttributeMatchCriterion>(); } Loading @@ -81,18 +154,80 @@ public class AudioMixingRule { * Add a rule for the selection of which streams are mixed together. * @param attrToMatch a non-null AudioAttributes instance for which a contradictory * rule hasn't been set yet. * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE}, * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}. * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}. * @return the same Builder instance. * @throws IllegalArgumentException */ @SystemApi public Builder addRule(AudioAttributes attrToMatch, int rule) throws IllegalArgumentException { if (!isValidSystemApiRule(rule)) { throw new IllegalArgumentException("Illegal rule value " + rule); } return addRuleInt(attrToMatch, rule); } /** * Add a rule by exclusion for the selection of which streams are mixed together. * <br>For instance the following code * <br><pre> * AudioAttributes mediaAttr = new AudioAttributes.Builder() * .setUsage(AudioAttributes.USAGE_MEDIA) * .build(); * AudioMixingRule noMediaRule = new AudioMixingRule.Builder() * .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE) * .build(); * </pre> * <br>will create a rule which maps to any usage value, except USAGE_MEDIA. * @param attrToMatch a non-null AudioAttributes instance for which a contradictory * rule hasn't been set yet. * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}. * @return the same Builder instance. * @throws IllegalArgumentException */ @SystemApi public Builder excludeRule(AudioAttributes attrToMatch, int rule) throws IllegalArgumentException { if (!isValidSystemApiRule(rule)) { throw new IllegalArgumentException("Illegal rule value " + rule); } return addRuleInt(attrToMatch, rule | RULE_EXCLUSION_MASK); } /** * Add or exclude a rule for the selection of which streams are mixed together. * @param attrToMatch a non-null AudioAttributes instance for which a contradictory * rule hasn't been set yet. * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE}, * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or * {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}. * @return the same Builder instance. * @throws IllegalArgumentException */ Builder addRuleInt(AudioAttributes attrToMatch, int rule) throws IllegalArgumentException { if (attrToMatch == null) { throw new IllegalArgumentException("Illegal null AudioAttributes argument"); } if ((rule != RULE_MATCH_ATTRIBUTE_USAGE) && (rule != RULE_EXCLUDE_ATTRIBUTE_USAGE)) { if (!isValidIntRule(rule)) { throw new IllegalArgumentException("Illegal rule value " + rule); } else { // as rules are added to the Builder, we verify they are consistent with the type // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID. if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) { if (isPlayerRule(rule)) { mTargetMixType = AudioMix.MIX_TYPE_PLAYERS; } else { mTargetMixType = AudioMix.MIX_TYPE_RECORDERS; } } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule)) || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule))) { throw new IllegalArgumentException("Incompatible rule for mix"); } } synchronized (mCriteria) { Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator(); Loading @@ -111,6 +246,19 @@ public class AudioMixingRule { + attrToMatch); } } } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET) || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) { // "capture preset"-base rule if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) { if (criterion.mRule == rule) { // rule already exists, we're done return this; } else { // criterion already exists with a another rule, it is incompatible throw new IllegalArgumentException("Contradictory rule exists for " + attrToMatch); } } } } // rule didn't exist, add it Loading @@ -119,13 +267,32 @@ public class AudioMixingRule { return this; } Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException { int rule = in.readInt(); AudioAttributes attr; if ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) { int usage = in.readInt(); attr = new AudioAttributes.Builder() .setUsage(usage).build(); } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET) || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) { int preset = in.readInt(); attr = new AudioAttributes.Builder() .setInternalCapturePreset(preset).build(); } else { in.readInt(); // assume there was in int value to read as for now they come in pair throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel"); } return addRuleInt(attr, rule); } /** * Combines all of the matching and exclusion rules that have been set and return a new * {@link AudioMixingRule} object. * @return a new {@link AudioMixingRule} object */ public AudioMixingRule build() { return new AudioMixingRule(mCriteria); return new AudioMixingRule(mTargetMixType, mCriteria); } } } Loading
media/java/android/media/AudioAttributes.java +2 −1 Original line number Diff line number Diff line Loading @@ -241,11 +241,11 @@ public final class AudioAttributes implements Parcelable { /** * @hide * CANDIDATE FOR PUBLIC API * Return the capture preset. * @return one of the values that can be set in {@link Builder#setCapturePreset(int)} or a * negative value if none has been set. */ @SystemApi public int getCapturePreset() { return mSource; } Loading Loading @@ -508,6 +508,7 @@ public final class AudioAttributes implements Parcelable { * {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}. * @return the same Builder instance. */ @SystemApi public Builder setCapturePreset(int preset) { switch (preset) { case MediaRecorder.AudioSource.DEFAULT: Loading
media/java/android/media/AudioManager.java +8 −6 Original line number Diff line number Diff line Loading @@ -2459,6 +2459,7 @@ public class AudioManager { * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)} * @throws IllegalArgumentException */ @SystemApi public int requestAudioFocus(OnAudioFocusChangeListener l, @NonNull AudioAttributes requestAttributes, int durationHint, Loading Loading @@ -2853,17 +2854,17 @@ public class AudioManager { /** * @hide * CANDIDATE FOR PUBLIC API * Register the given {@link AudioPolicy}. * This call is synchronous and blocks until the registration process successfully completed * or failed to complete. * @param policy the {@link AudioPolicy} to register. * @param policy the non-null {@link AudioPolicy} to register. * @return {@link #ERROR} if there was an error communicating with the registration service * or if the user doesn't have the required * {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission, * {@link #SUCCESS} otherwise. */ public int registerAudioPolicy(AudioPolicy policy) { @SystemApi public int registerAudioPolicy(@NonNull AudioPolicy policy) { if (policy == null) { throw new IllegalArgumentException("Illegal null AudioPolicy argument"); } Loading @@ -2885,16 +2886,17 @@ public class AudioManager { /** * @hide * CANDIDATE FOR PUBLIC API * @param policy the {@link AudioPolicy} to unregister. * @param policy the non-null {@link AudioPolicy} to unregister. */ public void unregisterAudioPolicyAsync(AudioPolicy policy) { @SystemApi public void unregisterAudioPolicyAsync(@NonNull AudioPolicy policy) { if (policy == null) { throw new IllegalArgumentException("Illegal null AudioPolicy argument"); } IAudioService service = getService(); try { service.unregisterAudioPolicyAsync(policy.token()); policy.setRegistration(null); } catch (RemoteException e) { Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e); } Loading
media/java/android/media/AudioService.java +23 −6 Original line number Diff line number Diff line Loading @@ -5538,6 +5538,8 @@ public class AudioService extends IAudioService.Stub { pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs); pw.print(" mMcc="); pw.println(mMcc); pw.print(" mHasVibrator="); pw.println(mHasVibrator); dumpAudioPolicies(pw); } private static String safeMediaVolumeStateToString(Integer state) { Loading Loading @@ -5797,6 +5799,10 @@ public class AudioService extends IAudioService.Stub { } synchronized (mAudioPolicies) { try { if (mAudioPolicies.containsKey(cb)) { Slog.e(TAG, "Cannot re-register policy"); return null; } AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb); cb.linkToDeath(app, 0/*flags*/); regId = app.connectMixes(); Loading @@ -5817,6 +5823,7 @@ public class AudioService extends IAudioService.Stub { if (app == null) { Slog.w(TAG, "Trying to unregister unknown audio policy for pid " + Binder.getCallingPid() + " / uid " + Binder.getCallingUid()); return; } else { cb.unlinkToDeath(app, 0/*flags*/); } Loading @@ -5825,12 +5832,21 @@ public class AudioService extends IAudioService.Stub { // TODO implement clearing mix attribute matching info in native audio policy } private void dumpAudioPolicies(PrintWriter pw) { pw.println("\nAudio policies:"); synchronized (mAudioPolicies) { for(AudioPolicyProxy policy : mAudioPolicies.values()) { pw.println(policy.toLogFriendlyString()); } } } //====================== // Audio policy proxy //====================== /** * This internal class inherits from AudioPolicyConfig which contains all the mixes and * their configurations. * This internal class inherits from AudioPolicyConfig, each instance contains all the * mixes of an AudioPolicy and their configurations. */ public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient { private static final String TAG = "AudioPolicyProxy"; Loading @@ -5838,7 +5854,7 @@ public class AudioService extends IAudioService.Stub { IBinder mToken; AudioPolicyProxy(AudioPolicyConfig config, IBinder token) { super(config); setRegistration(new String(config.toString() + ":ap:" + mAudioPolicyCounter++)); setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++)); mToken = token; } Loading @@ -5852,7 +5868,7 @@ public class AudioService extends IAudioService.Stub { String connectMixes() { updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE); return mRegistrationId; return getRegistration(); } void disconnectMixes() { Loading @@ -5863,8 +5879,9 @@ public class AudioService extends IAudioService.Stub { for (AudioMix mix : mMixes) { // TODO implement sending the mix attribute matching info to native audio policy if (DEBUG_AP) { Log.v(TAG, "AudioPolicyProxy connect mix state=" + connectionState + " addr=" + mix.getRegistration()); } Log.v(TAG, "AudioPolicyProxy mix new connection state=" + connectionState + " addr=" + mix.getRegistration()); } AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, connectionState, mix.getRegistration()); Loading
media/java/android/media/audiopolicy/AudioMix.java +40 −1 Original line number Diff line number Diff line Loading @@ -17,21 +17,25 @@ package android.media.audiopolicy; import android.annotation.IntDef; import android.annotation.SystemApi; import android.media.AudioFormat; import android.media.AudioSystem; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** * @hide */ @SystemApi public class AudioMix { private AudioMixingRule mRule; private AudioFormat mFormat; private int mRouteFlags; private String mRegistrationId; private int mMixType = MIX_TYPE_INVALID; /** * All parameters are guaranteed valid through the Builder. Loading @@ -41,20 +45,39 @@ public class AudioMix { mFormat = format; mRouteFlags = routeFlags; mRegistrationId = null; mMixType = rule.getTargetMixType(); } /** * An audio mix behavior where the output of the mix is sent to the original destination of * the audio signal, i.e. an output device for an output mix, or a recording for an input mix. */ @SystemApi public static final int ROUTE_FLAG_RENDER = 0x1; /** * An audio mix behavior where the output of the mix is rerouted back to the framework and * is accessible for injection or capture through the {@link Audiotrack} and {@link AudioRecord} * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord} * APIs. */ @SystemApi public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1; /** * @hide * Invalid mix type, default value. */ public static final int MIX_TYPE_INVALID = -1; /** * @hide * Mix type indicating playback streams are mixed. */ public static final int MIX_TYPE_PLAYERS = 0; /** * @hide * Mix type indicating recording streams are mixed. */ public static final int MIX_TYPE_RECORDERS = 1; int getRouteFlags() { return mRouteFlags; } Loading @@ -67,6 +90,11 @@ public class AudioMix { return mRule; } /** @hide */ public int getMixType() { return mMixType; } void setRegistration(String regId) { mRegistrationId = regId; } Loading @@ -76,6 +104,12 @@ public class AudioMix { return mRegistrationId; } /** @hide */ @Override public int hashCode() { return Objects.hash(mRouteFlags, mRule, mMixType, mFormat); } /** @hide */ @IntDef(flag = true, value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } ) Loading @@ -86,6 +120,7 @@ public class AudioMix { * Builder class for {@link AudioMix} objects * */ @SystemApi public static class Builder { private AudioMixingRule mRule = null; private AudioFormat mFormat = null; Loading @@ -102,6 +137,7 @@ public class AudioMix { * @param rule a non-null {@link AudioMixingRule} instance. * @throws IllegalArgumentException */ @SystemApi public Builder(AudioMixingRule rule) throws IllegalArgumentException { if (rule == null) { Loading Loading @@ -132,6 +168,7 @@ public class AudioMix { * @return the same Builder instance. * @throws IllegalArgumentException */ @SystemApi public Builder setFormat(AudioFormat format) throws IllegalArgumentException { if (format == null) { Loading @@ -148,6 +185,7 @@ public class AudioMix { * @return the same Builder instance. * @throws IllegalArgumentException */ @SystemApi public Builder setRouteFlags(@RouteFlags int routeFlags) throws IllegalArgumentException { if (routeFlags == 0) { Loading @@ -166,6 +204,7 @@ public class AudioMix { * @return a new {@link AudioMix} object * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set. */ @SystemApi public AudioMix build() throws IllegalArgumentException { if (mRule == null) { throw new IllegalArgumentException("Illegal null AudioMixingRule"); Loading
media/java/android/media/audiopolicy/AudioMixingRule.java +177 −10 Original line number Diff line number Diff line Loading @@ -16,10 +16,13 @@ package android.media.audiopolicy; import android.annotation.SystemApi; import android.media.AudioAttributes; import android.os.Parcel; import java.util.ArrayList; import java.util.Iterator; import java.util.Objects; /** Loading @@ -35,44 +38,114 @@ import java.util.Iterator; * .build(); * </pre> */ @SystemApi public class AudioMixingRule { private AudioMixingRule(ArrayList<AttributeMatchCriterion> criteria) { private AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria) { mCriteria = criteria; mTargetMixType = mixType; } /** * A rule requiring the usage information of the {@link AudioAttributes} to match * A rule requiring the usage information of the {@link AudioAttributes} to match. */ @SystemApi public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1; /** * A rule requiring the usage information of the {@link AudioAttributes} to differ * A rule requiring the capture preset information of the {@link AudioAttributes} to match. */ public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = 0x1 << 1; @SystemApi public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1; private final static int RULE_EXCLUSION_MASK = 0x8000; /** * @hide * A rule requiring the usage information of the {@link AudioAttributes} to differ. */ public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE; /** * @hide * A rule requiring the capture preset information of the {@link AudioAttributes} to differ. */ public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET = RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET; static final class AttributeMatchCriterion { AudioAttributes mAttr; int mRule; /** input parameters must be valid */ AttributeMatchCriterion(AudioAttributes attributes, int rule) { mAttr = attributes; mRule = rule; } @Override public int hashCode() { return Objects.hash(mAttr, mRule); } private ArrayList<AttributeMatchCriterion> mCriteria; void writeToParcel(Parcel dest) { dest.writeInt(mRule); if ((mRule == RULE_MATCH_ATTRIBUTE_USAGE) || (mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) { dest.writeInt(mAttr.getUsage()); } else { // capture preset rule dest.writeInt(mAttr.getCapturePreset()); } } } private final int mTargetMixType; int getTargetMixType() { return mTargetMixType; } private final ArrayList<AttributeMatchCriterion> mCriteria; ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; } @Override public int hashCode() { return Objects.hash(mTargetMixType, mCriteria); } private static boolean isValidSystemApiRule(int rule) { switch(rule) { case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: return true; default: return false; } } private static boolean isValidIntRule(int rule) { switch(rule) { case RULE_MATCH_ATTRIBUTE_USAGE: case RULE_EXCLUDE_ATTRIBUTE_USAGE: case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: case RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET: return true; default: return false; } } private static boolean isPlayerRule(int rule) { return ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)); } /** * Builder class for {@link AudioMixingRule} objects * */ @SystemApi public static class Builder { private ArrayList<AttributeMatchCriterion> mCriteria; private int mTargetMixType = AudioMix.MIX_TYPE_INVALID; /** * Constructs a new Builder with no rules. */ @SystemApi public Builder() { mCriteria = new ArrayList<AttributeMatchCriterion>(); } Loading @@ -81,18 +154,80 @@ public class AudioMixingRule { * Add a rule for the selection of which streams are mixed together. * @param attrToMatch a non-null AudioAttributes instance for which a contradictory * rule hasn't been set yet. * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE}, * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}. * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}. * @return the same Builder instance. * @throws IllegalArgumentException */ @SystemApi public Builder addRule(AudioAttributes attrToMatch, int rule) throws IllegalArgumentException { if (!isValidSystemApiRule(rule)) { throw new IllegalArgumentException("Illegal rule value " + rule); } return addRuleInt(attrToMatch, rule); } /** * Add a rule by exclusion for the selection of which streams are mixed together. * <br>For instance the following code * <br><pre> * AudioAttributes mediaAttr = new AudioAttributes.Builder() * .setUsage(AudioAttributes.USAGE_MEDIA) * .build(); * AudioMixingRule noMediaRule = new AudioMixingRule.Builder() * .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE) * .build(); * </pre> * <br>will create a rule which maps to any usage value, except USAGE_MEDIA. * @param attrToMatch a non-null AudioAttributes instance for which a contradictory * rule hasn't been set yet. * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}. * @return the same Builder instance. * @throws IllegalArgumentException */ @SystemApi public Builder excludeRule(AudioAttributes attrToMatch, int rule) throws IllegalArgumentException { if (!isValidSystemApiRule(rule)) { throw new IllegalArgumentException("Illegal rule value " + rule); } return addRuleInt(attrToMatch, rule | RULE_EXCLUSION_MASK); } /** * Add or exclude a rule for the selection of which streams are mixed together. * @param attrToMatch a non-null AudioAttributes instance for which a contradictory * rule hasn't been set yet. * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE}, * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}, * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or * {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}. * @return the same Builder instance. * @throws IllegalArgumentException */ Builder addRuleInt(AudioAttributes attrToMatch, int rule) throws IllegalArgumentException { if (attrToMatch == null) { throw new IllegalArgumentException("Illegal null AudioAttributes argument"); } if ((rule != RULE_MATCH_ATTRIBUTE_USAGE) && (rule != RULE_EXCLUDE_ATTRIBUTE_USAGE)) { if (!isValidIntRule(rule)) { throw new IllegalArgumentException("Illegal rule value " + rule); } else { // as rules are added to the Builder, we verify they are consistent with the type // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID. if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) { if (isPlayerRule(rule)) { mTargetMixType = AudioMix.MIX_TYPE_PLAYERS; } else { mTargetMixType = AudioMix.MIX_TYPE_RECORDERS; } } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule)) || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule))) { throw new IllegalArgumentException("Incompatible rule for mix"); } } synchronized (mCriteria) { Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator(); Loading @@ -111,6 +246,19 @@ public class AudioMixingRule { + attrToMatch); } } } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET) || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) { // "capture preset"-base rule if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) { if (criterion.mRule == rule) { // rule already exists, we're done return this; } else { // criterion already exists with a another rule, it is incompatible throw new IllegalArgumentException("Contradictory rule exists for " + attrToMatch); } } } } // rule didn't exist, add it Loading @@ -119,13 +267,32 @@ public class AudioMixingRule { return this; } Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException { int rule = in.readInt(); AudioAttributes attr; if ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) { int usage = in.readInt(); attr = new AudioAttributes.Builder() .setUsage(usage).build(); } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET) || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) { int preset = in.readInt(); attr = new AudioAttributes.Builder() .setInternalCapturePreset(preset).build(); } else { in.readInt(); // assume there was in int value to read as for now they come in pair throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel"); } return addRuleInt(attr, rule); } /** * Combines all of the matching and exclusion rules that have been set and return a new * {@link AudioMixingRule} object. * @return a new {@link AudioMixingRule} object */ public AudioMixingRule build() { return new AudioMixingRule(mCriteria); return new AudioMixingRule(mTargetMixType, mCriteria); } } }