Merge "AudioMix address and type, rule exclusion API, dynamic source" into lmp-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
57457b98cf
@@ -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:
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ArrayList<AttributeMatchCriterion> mCriteria;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_UNREGISTERED;
|
||||||
mStatus = POLICY_STATUS_INVALID;
|
|
||||||
} else {
|
|
||||||
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,17 +238,17 @@ 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.
|
||||||
* @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
|
* @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
|
||||||
* {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
|
* {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
|
||||||
* @returna new {@link AudioTrack} instance whose data format is the one defined in the
|
* @return a new {@link AudioTrack} instance whose data format is the one defined in the
|
||||||
* {@link AudioMix}, or null if this policy was not successfully registered
|
* {@link AudioMix}, or null if this policy was not successfully registered
|
||||||
* 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
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user