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;
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
@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
private AudioFormat mFormat = null;
private int mRouteFlags = 0;
private int mCallbackFlags = 0;
+ private int mDeviceId = -1;
+ private String mDeviceAddress = null;
/**
* @hide
* @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");
* @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());
}
/**
+ * @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.
* @return the same Builder instance.
}
/**
- * 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.
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
* @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
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?
}
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);
}
}
}
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;
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());
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();
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("");
}