am 57457b98: Merge "AudioMix address and type, rule exclusion API, dynamic source" into lmp-mr1-dev

* commit '57457b98cfec197283c42585a3bc4abb1b21d05b':
  AudioMix address and type, rule exclusion API, dynamic source
This commit is contained in:
Jean-Michel Trivi
2014-12-02 17:17:06 +00:00
committed by Android Git Automerger
7 changed files with 398 additions and 67 deletions

View File

@@ -241,11 +241,11 @@ public final class AudioAttributes implements Parcelable {
/** /**
* @hide * @hide
* CANDIDATE FOR PUBLIC API
* Return the capture preset. * Return the capture preset.
* @return one of the values that can be set in {@link Builder#setCapturePreset(int)} or a * @return one of the values that can be set in {@link Builder#setCapturePreset(int)} or a
* negative value if none has been set. * negative value if none has been set.
*/ */
@SystemApi
public int getCapturePreset() { public int getCapturePreset() {
return mSource; return mSource;
} }
@@ -508,6 +508,7 @@ public final class AudioAttributes implements Parcelable {
* {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}. * {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
* @return the same Builder instance. * @return the same Builder instance.
*/ */
@SystemApi
public Builder setCapturePreset(int preset) { public Builder setCapturePreset(int preset) {
switch (preset) { switch (preset) {
case MediaRecorder.AudioSource.DEFAULT: case MediaRecorder.AudioSource.DEFAULT:

View File

@@ -2459,6 +2459,7 @@ public class AudioManager {
* {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)} * {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
@SystemApi
public int requestAudioFocus(OnAudioFocusChangeListener l, public int requestAudioFocus(OnAudioFocusChangeListener l,
@NonNull AudioAttributes requestAttributes, @NonNull AudioAttributes requestAttributes,
int durationHint, int durationHint,
@@ -2853,17 +2854,17 @@ public class AudioManager {
/** /**
* @hide * @hide
* CANDIDATE FOR PUBLIC API
* Register the given {@link AudioPolicy}. * Register the given {@link AudioPolicy}.
* This call is synchronous and blocks until the registration process successfully completed * This call is synchronous and blocks until the registration process successfully completed
* or failed to complete. * 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 * @return {@link #ERROR} if there was an error communicating with the registration service
* or if the user doesn't have the required * or if the user doesn't have the required
* {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission, * {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission,
* {@link #SUCCESS} otherwise. * {@link #SUCCESS} otherwise.
*/ */
public int registerAudioPolicy(AudioPolicy policy) { @SystemApi
public int registerAudioPolicy(@NonNull AudioPolicy policy) {
if (policy == null) { if (policy == null) {
throw new IllegalArgumentException("Illegal null AudioPolicy argument"); throw new IllegalArgumentException("Illegal null AudioPolicy argument");
} }
@@ -2885,16 +2886,17 @@ public class AudioManager {
/** /**
* @hide * @hide
* CANDIDATE FOR PUBLIC API * @param policy the non-null {@link AudioPolicy} to unregister.
* @param policy the {@link AudioPolicy} to unregister.
*/ */
public void unregisterAudioPolicyAsync(AudioPolicy policy) { @SystemApi
public void unregisterAudioPolicyAsync(@NonNull AudioPolicy policy) {
if (policy == null) { if (policy == null) {
throw new IllegalArgumentException("Illegal null AudioPolicy argument"); throw new IllegalArgumentException("Illegal null AudioPolicy argument");
} }
IAudioService service = getService(); IAudioService service = getService();
try { try {
service.unregisterAudioPolicyAsync(policy.token()); service.unregisterAudioPolicyAsync(policy.token());
policy.setRegistration(null);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e); Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
} }

View File

@@ -5538,6 +5538,8 @@ public class AudioService extends IAudioService.Stub {
pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs); pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs);
pw.print(" mMcc="); pw.println(mMcc); pw.print(" mMcc="); pw.println(mMcc);
pw.print(" mHasVibrator="); pw.println(mHasVibrator); pw.print(" mHasVibrator="); pw.println(mHasVibrator);
dumpAudioPolicies(pw);
} }
private static String safeMediaVolumeStateToString(Integer state) { private static String safeMediaVolumeStateToString(Integer state) {
@@ -5797,6 +5799,10 @@ public class AudioService extends IAudioService.Stub {
} }
synchronized (mAudioPolicies) { synchronized (mAudioPolicies) {
try { try {
if (mAudioPolicies.containsKey(cb)) {
Slog.e(TAG, "Cannot re-register policy");
return null;
}
AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb); AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
cb.linkToDeath(app, 0/*flags*/); cb.linkToDeath(app, 0/*flags*/);
regId = app.connectMixes(); regId = app.connectMixes();
@@ -5817,6 +5823,7 @@ public class AudioService extends IAudioService.Stub {
if (app == null) { if (app == null) {
Slog.w(TAG, "Trying to unregister unknown audio policy for pid " Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
+ Binder.getCallingPid() + " / uid " + Binder.getCallingUid()); + Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
return;
} else { } else {
cb.unlinkToDeath(app, 0/*flags*/); cb.unlinkToDeath(app, 0/*flags*/);
} }
@@ -5825,12 +5832,21 @@ public class AudioService extends IAudioService.Stub {
// TODO implement clearing mix attribute matching info in native audio policy // 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 // Audio policy proxy
//====================== //======================
/** /**
* This internal class inherits from AudioPolicyConfig which contains all the mixes and * This internal class inherits from AudioPolicyConfig, each instance contains all the
* their configurations. * mixes of an AudioPolicy and their configurations.
*/ */
public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient { public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
private static final String TAG = "AudioPolicyProxy"; private static final String TAG = "AudioPolicyProxy";
@@ -5838,7 +5854,7 @@ public class AudioService extends IAudioService.Stub {
IBinder mToken; IBinder mToken;
AudioPolicyProxy(AudioPolicyConfig config, IBinder token) { AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
super(config); super(config);
setRegistration(new String(config.toString() + ":ap:" + mAudioPolicyCounter++)); setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
mToken = token; mToken = token;
} }
@@ -5852,7 +5868,7 @@ public class AudioService extends IAudioService.Stub {
String connectMixes() { String connectMixes() {
updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE); updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
return mRegistrationId; return getRegistration();
} }
void disconnectMixes() { void disconnectMixes() {
@@ -5863,8 +5879,9 @@ public class AudioService extends IAudioService.Stub {
for (AudioMix mix : mMixes) { for (AudioMix mix : mMixes) {
// TODO implement sending the mix attribute matching info to native audio policy // TODO implement sending the mix attribute matching info to native audio policy
if (DEBUG_AP) { if (DEBUG_AP) {
Log.v(TAG, "AudioPolicyProxy connect mix state=" + connectionState Log.v(TAG, "AudioPolicyProxy mix new connection state=" + connectionState
+ " addr=" + mix.getRegistration()); } + " addr=" + mix.getRegistration());
}
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX,
connectionState, connectionState,
mix.getRegistration()); mix.getRegistration());

View File

@@ -17,21 +17,25 @@
package android.media.audiopolicy; package android.media.audiopolicy;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioSystem; import android.media.AudioSystem;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/** /**
* @hide * @hide
*/ */
@SystemApi
public class AudioMix { public class AudioMix {
private AudioMixingRule mRule; private AudioMixingRule mRule;
private AudioFormat mFormat; private AudioFormat mFormat;
private int mRouteFlags; private int mRouteFlags;
private String mRegistrationId; private String mRegistrationId;
private int mMixType = MIX_TYPE_INVALID;
/** /**
* All parameters are guaranteed valid through the Builder. * All parameters are guaranteed valid through the Builder.
@@ -41,20 +45,39 @@ public class AudioMix {
mFormat = format; mFormat = format;
mRouteFlags = routeFlags; mRouteFlags = routeFlags;
mRegistrationId = null; mRegistrationId = null;
mMixType = rule.getTargetMixType();
} }
/** /**
* An audio mix behavior where the output of the mix is sent to the original destination of * 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. * 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; 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 * 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. * APIs.
*/ */
@SystemApi
public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1; 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() { int getRouteFlags() {
return mRouteFlags; return mRouteFlags;
} }
@@ -67,6 +90,11 @@ public class AudioMix {
return mRule; return mRule;
} }
/** @hide */
public int getMixType() {
return mMixType;
}
void setRegistration(String regId) { void setRegistration(String regId) {
mRegistrationId = regId; mRegistrationId = regId;
} }
@@ -76,6 +104,12 @@ public class AudioMix {
return mRegistrationId; return mRegistrationId;
} }
/** @hide */
@Override
public int hashCode() {
return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
}
/** @hide */ /** @hide */
@IntDef(flag = true, @IntDef(flag = true,
value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } ) value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
@@ -86,6 +120,7 @@ public class AudioMix {
* Builder class for {@link AudioMix} objects * Builder class for {@link AudioMix} objects
* *
*/ */
@SystemApi
public static class Builder { public static class Builder {
private AudioMixingRule mRule = null; private AudioMixingRule mRule = null;
private AudioFormat mFormat = null; private AudioFormat mFormat = null;
@@ -102,6 +137,7 @@ public class AudioMix {
* @param rule a non-null {@link AudioMixingRule} instance. * @param rule a non-null {@link AudioMixingRule} instance.
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
@SystemApi
public Builder(AudioMixingRule rule) public Builder(AudioMixingRule rule)
throws IllegalArgumentException { throws IllegalArgumentException {
if (rule == null) { if (rule == null) {
@@ -132,6 +168,7 @@ public class AudioMix {
* @return the same Builder instance. * @return the same Builder instance.
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
@SystemApi
public Builder setFormat(AudioFormat format) public Builder setFormat(AudioFormat format)
throws IllegalArgumentException { throws IllegalArgumentException {
if (format == null) { if (format == null) {
@@ -148,6 +185,7 @@ public class AudioMix {
* @return the same Builder instance. * @return the same Builder instance.
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
@SystemApi
public Builder setRouteFlags(@RouteFlags int routeFlags) public Builder setRouteFlags(@RouteFlags int routeFlags)
throws IllegalArgumentException { throws IllegalArgumentException {
if (routeFlags == 0) { if (routeFlags == 0) {
@@ -166,6 +204,7 @@ public class AudioMix {
* @return a new {@link AudioMix} object * @return a new {@link AudioMix} object
* @throws IllegalArgumentException if no {@link AudioMixingRule} has been set. * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
*/ */
@SystemApi
public AudioMix build() throws IllegalArgumentException { public AudioMix build() throws IllegalArgumentException {
if (mRule == null) { if (mRule == null) {
throw new IllegalArgumentException("Illegal null AudioMixingRule"); throw new IllegalArgumentException("Illegal null AudioMixingRule");

View File

@@ -16,10 +16,13 @@
package android.media.audiopolicy; package android.media.audiopolicy;
import android.annotation.SystemApi;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.os.Parcel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.Objects;
/** /**
@@ -35,44 +38,114 @@ import java.util.Iterator;
* .build(); * .build();
* </pre> * </pre>
*/ */
@SystemApi
public class AudioMixingRule { public class AudioMixingRule {
private AudioMixingRule(ArrayList<AttributeMatchCriterion> criteria) { private AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria) {
mCriteria = 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; 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 { static final class AttributeMatchCriterion {
AudioAttributes mAttr; AudioAttributes mAttr;
int mRule; int mRule;
/** input parameters must be valid */
AttributeMatchCriterion(AudioAttributes attributes, int rule) { AttributeMatchCriterion(AudioAttributes attributes, int rule) {
mAttr = attributes; mAttr = attributes;
mRule = rule; 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; } 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 * Builder class for {@link AudioMixingRule} objects
*
*/ */
@SystemApi
public static class Builder { public static class Builder {
private ArrayList<AttributeMatchCriterion> mCriteria; private ArrayList<AttributeMatchCriterion> mCriteria;
private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
/** /**
* Constructs a new Builder with no rules. * Constructs a new Builder with no rules.
*/ */
@SystemApi
public Builder() { public Builder() {
mCriteria = new ArrayList<AttributeMatchCriterion>(); mCriteria = new ArrayList<AttributeMatchCriterion>();
} }
@@ -81,18 +154,80 @@ public class AudioMixingRule {
* Add a rule for the selection of which streams are mixed together. * Add a rule for the selection of which streams are mixed together.
* @param attrToMatch a non-null AudioAttributes instance for which a contradictory * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
* rule hasn't been set yet. * rule hasn't been set yet.
* @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE}, * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
* {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}. * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
* @return the same Builder instance. * @return the same Builder instance.
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
@SystemApi
public Builder addRule(AudioAttributes attrToMatch, int rule) public Builder addRule(AudioAttributes attrToMatch, int rule)
throws IllegalArgumentException { 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) { if (attrToMatch == null) {
throw new IllegalArgumentException("Illegal null AudioAttributes argument"); 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); 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) { synchronized (mCriteria) {
Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator(); Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator();
@@ -111,6 +246,19 @@ public class AudioMixingRule {
+ attrToMatch); + 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 // rule didn't exist, add it
@@ -119,13 +267,32 @@ public class AudioMixingRule {
return this; 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 * Combines all of the matching and exclusion rules that have been set and return a new
* {@link AudioMixingRule} object. * {@link AudioMixingRule} object.
* @return a new {@link AudioMixingRule} object * @return a new {@link AudioMixingRule} object
*/ */
public AudioMixingRule build() { public AudioMixingRule build() {
return new AudioMixingRule(mCriteria); return new AudioMixingRule(mTargetMixType, mCriteria);
} }
} }
} }

View File

@@ -17,17 +17,21 @@
package android.media.audiopolicy; package android.media.audiopolicy;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.AudioRecord; import android.media.AudioRecord;
import android.media.AudioSystem;
import android.media.AudioTrack; import android.media.AudioTrack;
import android.media.MediaRecorder; import android.media.MediaRecorder;
import android.os.Binder; import android.os.Binder;
import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log; import android.util.Log;
import android.util.Slog; import android.util.Slog;
@@ -39,21 +43,20 @@ import java.util.ArrayList;
* @hide * @hide
* AudioPolicy provides access to the management of audio routing and audio focus. * AudioPolicy provides access to the management of audio routing and audio focus.
*/ */
@SystemApi
public class AudioPolicy { public class AudioPolicy {
private static final String TAG = "AudioPolicy"; private static final String TAG = "AudioPolicy";
/**
* The status of an audio policy that cannot be used because it is invalid.
*/
public static final int POLICY_STATUS_INVALID = 0;
/** /**
* The status of an audio policy that is valid but cannot be used because it is not registered. * The status of an audio policy that is valid but cannot be used because it is not registered.
*/ */
@SystemApi
public static final int POLICY_STATUS_UNREGISTERED = 1; public static final int POLICY_STATUS_UNREGISTERED = 1;
/** /**
* The status of an audio policy that is valid, successfully registered and thus active. * The status of an audio policy that is valid, successfully registered and thus active.
*/ */
@SystemApi
public static final int POLICY_STATUS_REGISTERED = 2; public static final int POLICY_STATUS_REGISTERED = 2;
private int mStatus; private int mStatus;
@@ -72,22 +75,29 @@ public class AudioPolicy {
/** /**
* The parameter is guaranteed non-null through the Builder * The parameter is guaranteed non-null through the Builder
*/ */
private AudioPolicy(AudioPolicyConfig config, Context context) { private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper) {
mConfig = config; mConfig = config;
if (mConfig.mMixes.isEmpty()) {
mStatus = POLICY_STATUS_INVALID;
} else {
mStatus = POLICY_STATUS_UNREGISTERED; mStatus = POLICY_STATUS_UNREGISTERED;
}
mContext = context; mContext = context;
if (looper == null) {
looper = Looper.getMainLooper();
}
if (looper != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
Log.e(TAG, "No event handler due to looper without a thread");
}
} }
/** /**
* Builder class for {@link AudioPolicy} objects * Builder class for {@link AudioPolicy} objects
*/ */
@SystemApi
public static class Builder { public static class Builder {
private ArrayList<AudioMix> mMixes; private ArrayList<AudioMix> mMixes;
private Context mContext; private Context mContext;
private Looper mLooper;
/** /**
* Constructs a new Builder with no audio mixes. * Constructs a new Builder with no audio mixes.
@@ -104,7 +114,7 @@ public class AudioPolicy {
* @return the same Builder instance. * @return the same Builder instance.
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
public Builder addMix(AudioMix mix) throws IllegalArgumentException { public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
if (mix == null) { if (mix == null) {
throw new IllegalArgumentException("Illegal null AudioMix argument"); throw new IllegalArgumentException("Illegal null AudioMix argument");
} }
@@ -112,18 +122,41 @@ public class AudioPolicy {
return this; return this;
} }
/**
* Sets the {@link Looper} on which to run the event loop.
* @param looper a non-null specific Looper.
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
if (looper == null) {
throw new IllegalArgumentException("Illegal null Looper argument");
}
mLooper = looper;
return this;
}
public AudioPolicy build() { public AudioPolicy build() {
return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext); return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper);
} }
} }
/** @hide */
public void setRegistration(String regId) { public void setRegistration(String regId) {
mRegistrationId = regId; mRegistrationId = regId;
mConfig.setRegistration(regId); mConfig.setRegistration(regId);
if (regId != null) {
mStatus = POLICY_STATUS_REGISTERED;
} else {
mStatus = POLICY_STATUS_UNREGISTERED;
}
sendMsg(mEventHandler, MSG_POLICY_STATUS_CHANGE);
} }
private boolean policyReadyToUse() { private boolean policyReadyToUse() {
if (mStatus != POLICY_STATUS_REGISTERED) {
Log.e(TAG, "Cannot use unregistered AudioPolicy");
return false;
}
if (mContext == null) { if (mContext == null) {
Log.e(TAG, "Cannot use AudioPolicy without context"); Log.e(TAG, "Cannot use AudioPolicy without context");
return false; return false;
@@ -155,11 +188,17 @@ public class AudioPolicy {
{ {
throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back"); throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
} }
// TODO also check mix is defined for playback or recording, and matches forTrack argument if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
throw new IllegalArgumentException(
"Invalid AudioMix: not defined for being a recording source");
}
if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
throw new IllegalArgumentException(
"Invalid AudioMix: not defined for capturing playback");
}
} }
/** /**
* @hide
* Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}. * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
* Audio buffers recorded through the created instance will contain the mix of the audio * Audio buffers recorded through the created instance will contain the mix of the audio
* streams that fed the given mixer. * streams that fed the given mixer.
@@ -170,6 +209,7 @@ public class AudioPolicy {
* with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
@SystemApi
public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException { public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
if (!policyReadyToUse()) { if (!policyReadyToUse()) {
Log.e(TAG, "Cannot create AudioRecord sink for AudioMix"); Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
@@ -186,7 +226,7 @@ public class AudioPolicy {
AudioRecord ar = new AudioRecord( AudioRecord ar = new AudioRecord(
new AudioAttributes.Builder() new AudioAttributes.Builder()
.setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX) .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
.addTag(mix.getRegistration()) .addTag(addressForTag(mix))
.build(), .build(),
mixFormat, mixFormat,
AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(), AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
@@ -198,7 +238,6 @@ public class AudioPolicy {
} }
/** /**
* @hide
* Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}. * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
* Audio buffers played through the created instance will be sent to the given mix * Audio buffers played through the created instance will be sent to the given mix
* to be recorded through the recording APIs. * to be recorded through the recording APIs.
@@ -209,6 +248,7 @@ public class AudioPolicy {
* with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
@SystemApi
public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException { public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
if (!policyReadyToUse()) { if (!policyReadyToUse()) {
Log.e(TAG, "Cannot create AudioTrack source for AudioMix"); Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
@@ -219,7 +259,7 @@ public class AudioPolicy {
AudioTrack at = new AudioTrack( AudioTrack at = new AudioTrack(
new AudioAttributes.Builder() new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE) .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
.addTag(mix.getRegistration()) .addTag(addressForTag(mix))
.build(), .build(),
mix.getFormat(), mix.getFormat(),
AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(), AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
@@ -230,20 +270,63 @@ public class AudioPolicy {
return at; return at;
} }
@SystemApi
public int getStatus() { public int getStatus() {
return mStatus; return mStatus;
} }
@SystemApi
public static abstract class AudioPolicyStatusListener { public static abstract class AudioPolicyStatusListener {
void onStatusChange() {} public void onStatusChange() {}
void onMixStateUpdate(AudioMix mix) {} public void onMixStateUpdate(AudioMix mix) {}
} }
void setStatusListener(AudioPolicyStatusListener l) { @SystemApi
synchronized public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
mStatusListener = l; mStatusListener = l;
} }
/** @hide */ synchronized private void onPolicyStatusChange() {
if (mStatusListener == null) {
return;
}
mStatusListener.onStatusChange();
}
//==================================================
// Event handling
private final EventHandler mEventHandler;
private final static int MSG_POLICY_STATUS_CHANGE = 0;
private class EventHandler extends Handler {
public EventHandler(AudioPolicy ap, Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_POLICY_STATUS_CHANGE:
onPolicyStatusChange();
break;
default:
Log.e(TAG, "Unknown event " + msg.what);
}
}
}
//==========================================================
// Utils
private static String addressForTag(AudioMix mix) {
return "addr=" + mix.getRegistration();
}
private static void sendMsg(Handler handler, int msg) {
if (handler != null) {
handler.sendEmptyMessage(msg);
}
}
public String toLogFriendlyString() { public String toLogFriendlyString() {
String textDump = new String("android.media.audiopolicy.AudioPolicy:\n"); String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
textDump += "config=" + mConfig.toLogFriendlyString(); textDump += "config=" + mConfig.toLogFriendlyString();
@@ -252,7 +335,6 @@ public class AudioPolicy {
/** @hide */ /** @hide */
@IntDef({ @IntDef({
POLICY_STATUS_INVALID,
POLICY_STATUS_REGISTERED, POLICY_STATUS_REGISTERED,
POLICY_STATUS_UNREGISTERED POLICY_STATUS_UNREGISTERED
}) })

View File

@@ -27,6 +27,7 @@ import android.os.Parcelable;
import android.util.Log; import android.util.Log;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects;
/** /**
* @hide * @hide
@@ -38,7 +39,7 @@ public class AudioPolicyConfig implements Parcelable {
protected ArrayList<AudioMix> mMixes; protected ArrayList<AudioMix> mMixes;
protected String mRegistrationId = null; private String mRegistrationId = null;
protected AudioPolicyConfig(AudioPolicyConfig conf) { protected AudioPolicyConfig(AudioPolicyConfig conf) {
mMixes = conf.mMixes; mMixes = conf.mMixes;
@@ -61,6 +62,11 @@ public class AudioPolicyConfig implements Parcelable {
mMixes.add(mix); mMixes.add(mix);
} }
@Override
public int hashCode() {
return Objects.hash(mMixes);
}
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;
@@ -80,8 +86,7 @@ public class AudioPolicyConfig implements Parcelable {
final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria(); final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria();
dest.writeInt(criteria.size()); dest.writeInt(criteria.size());
for (AttributeMatchCriterion criterion : criteria) { for (AttributeMatchCriterion criterion : criteria) {
dest.writeInt(criterion.mRule); criterion.writeToParcel(dest);
dest.writeInt(criterion.mAttr.getUsage());
} }
} }
} }
@@ -106,17 +111,7 @@ public class AudioPolicyConfig implements Parcelable {
AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder(); AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
for (int j = 0 ; j < nbRules ; j++) { for (int j = 0 ; j < nbRules ; j++) {
// read the matching rules // read the matching rules
int matchRule = in.readInt(); ruleBuilder.addRuleFromParcel(in);
if ((matchRule == AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE)
|| (matchRule == AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)) {
int usage = in.readInt();
final AudioAttributes attr = new AudioAttributes.Builder()
.setUsage(usage).build();
ruleBuilder.addRule(attr, matchRule);
} else {
Log.w(TAG, "Encountered unsupported rule, skipping");
in.readInt();
}
} }
mixBuilder.setMixingRule(ruleBuilder.build()); mixBuilder.setMixingRule(ruleBuilder.build());
mMixes.add(mixBuilder.build()); mMixes.add(mixBuilder.build());
@@ -140,7 +135,7 @@ public class AudioPolicyConfig implements Parcelable {
public String toLogFriendlyString () { public String toLogFriendlyString () {
String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n"); String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
textDump += mMixes.size() + " AudioMix:\n"; textDump += mMixes.size() + " AudioMix: "+ mRegistrationId + "\n";
for(AudioMix mix : mMixes) { for(AudioMix mix : mMixes) {
// write mix route flags // write mix route flags
textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n"; textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
@@ -161,6 +156,14 @@ public class AudioPolicyConfig implements Parcelable {
textDump += " match usage "; textDump += " match usage ";
textDump += criterion.mAttr.usageToString(); textDump += criterion.mAttr.usageToString();
break; break;
case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
textDump += " exclude capture preset ";
textDump += criterion.mAttr.getCapturePreset();
break;
case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
textDump += " match capture preset ";
textDump += criterion.mAttr.getCapturePreset();
break;
default: default:
textDump += "invalid rule!"; textDump += "invalid rule!";
} }
@@ -170,12 +173,32 @@ public class AudioPolicyConfig implements Parcelable {
return textDump; return textDump;
} }
public void setRegistration(String regId) { protected void setRegistration(String regId) {
mRegistrationId = regId; final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty();
final boolean newRegNull = (regId == null) || regId.isEmpty();
if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) {
Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId);
return;
}
mRegistrationId = regId == null ? "" : regId;
int mixIndex = 0; int mixIndex = 0;
for (AudioMix mix : mMixes) { for (AudioMix mix : mMixes) {
mix.setRegistration(mRegistrationId + "mix:" + mixIndex++); if (!mRegistrationId.isEmpty()) {
mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
+ mixIndex++);
} else {
mix.setRegistration("");
}
} }
} }
private static String mixTypeId(int type) {
if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
else return "i";
}
protected String getRegistration() {
return mRegistrationId;
}
} }