summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--media/java/android/media/AudioAttributes.java7
-rw-r--r--media/java/android/media/AudioManager.java6
-rw-r--r--media/java/android/media/AudioService.java59
-rw-r--r--media/java/android/media/IAudioService.aidl2
-rw-r--r--media/java/android/media/audiopolicy/AudioMix.java13
-rw-r--r--media/java/android/media/audiopolicy/AudioMixingRule.java2
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java134
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java22
8 files changed, 218 insertions, 27 deletions
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 17d3251b3a6f..20c4978e779b 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -161,6 +161,12 @@ public final class AudioAttributes implements Parcelable {
* Usage value to use when the usage is for game audio.
*/
public final static int USAGE_GAME = 14;
+ /**
+ * @hide
+ * Usage value to use when feeding audio to the platform and replacing "traditional" audio
+ * source, such as audio capture devices.
+ */
+ public final static int USAGE_VIRTUAL_SOURCE = 15;
/**
* Flag defining a behavior where the audibility of the sound will be ensured by the system.
@@ -374,6 +380,7 @@ public final class AudioAttributes implements Parcelable {
case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
case USAGE_ASSISTANCE_SONIFICATION:
case USAGE_GAME:
+ case USAGE_VIRTUAL_SOURCE:
mUsage = usage;
break;
default:
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 716ff992c008..8fc0b8e4a86d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2663,9 +2663,13 @@ public class AudioManager {
}
IAudioService service = getService();
try {
- if (!service.registerAudioPolicy(policy.getConfig(), policy.token())) {
+ String regId = service.registerAudioPolicy(policy.getConfig(), policy.token());
+ if (regId == null) {
return ERROR;
+ } else {
+ policy.setRegistration(regId);
}
+ // successful registration
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerAudioPolicyAsync()", e);
return ERROR;
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 6a695170f3c0..2f683828d3b1 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -48,6 +48,7 @@ import android.hardware.hdmi.HdmiTvClient;
import android.hardware.usb.UsbManager;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
+import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicyConfig;
import android.media.session.MediaSessionLegacyHelper;
import android.os.Binder;
@@ -118,6 +119,10 @@ public class AudioService extends IAudioService.Stub {
/** Debug audio mode */
protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG);
+
+ /** Debug audio policy feature */
+ protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG);
+
/** Debug volumes */
protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG);
@@ -5634,31 +5639,33 @@ public class AudioService extends IAudioService.Stub {
//==========================================================================================
// Audio policy management
//==========================================================================================
- public boolean registerAudioPolicy(AudioPolicyConfig policyConfig, IBinder cb) {
+ public String registerAudioPolicy(AudioPolicyConfig policyConfig, IBinder cb) {
//Log.v(TAG, "registerAudioPolicy for " + cb + " got policy:" + policyConfig);
+ String regId = null;
boolean hasPermissionForPolicy =
(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_AUDIO_ROUTING));
if (!hasPermissionForPolicy) {
Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid "
+ Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
- return false;
+ return null;
}
synchronized (mAudioPolicies) {
- AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
try {
+ AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
cb.linkToDeath(app, 0/*flags*/);
+ regId = app.connectMixes();
mAudioPolicies.put(cb, app);
} catch (RemoteException e) {
// audio policy owner has already died!
Slog.w(TAG, "Audio policy registration failed, could not link to " + cb +
" binder death", e);
- return false;
+ return null;
}
}
- // TODO implement registration with native audio policy (including permission check)
- return true;
+ return regId;
}
+
public void unregisterAudioPolicyAsync(IBinder cb) {
synchronized (mAudioPolicies) {
AudioPolicyProxy app = mAudioPolicies.remove(cb);
@@ -5668,27 +5675,59 @@ public class AudioService extends IAudioService.Stub {
} else {
cb.unlinkToDeath(app, 0/*flags*/);
}
+ app.disconnectMixes();
}
- // TODO implement registration with native audio policy
+ // TODO implement clearing mix attribute matching info in native audio policy
}
- public class AudioPolicyProxy implements IBinder.DeathRecipient {
+ /**
+ * This internal class inherits from AudioPolicyConfig which contains all the mixes and
+ * their configurations.
+ */
+ public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
private static final String TAG = "AudioPolicyProxy";
AudioPolicyConfig mConfig;
IBinder mToken;
AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
- mConfig = config;
+ super(config);
+ setRegistration(new String(config.toString() + ":ap:" + mAudioPolicyCounter++));
mToken = token;
}
public void binderDied() {
synchronized (mAudioPolicies) {
- Log.v(TAG, "audio policy " + mToken + " died");
+ Log.i(TAG, "audio policy " + mToken + " died");
mAudioPolicies.remove(mToken);
+ disconnectMixes();
+ }
+ }
+
+ String connectMixes() {
+ updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
+ return mRegistrationId;
+ }
+
+ void disconnectMixes() {
+ updateMixes(AudioSystem.DEVICE_STATE_UNAVAILABLE);
+ }
+
+ void updateMixes(int connectionState) {
+ for (AudioMix mix : mMixes) {
+ // TODO implement sending the mix attribute matching info to native audio policy
+ if (DEBUG_AP) {
+ Log.v(TAG, "AudioPolicyProxy connect mix state=" + connectionState
+ + " addr=" + mix.getRegistration()); }
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX,
+ connectionState,
+ mix.getRegistration());
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX,
+ connectionState,
+ mix.getRegistration());
}
}
};
private HashMap<IBinder, AudioPolicyProxy> mAudioPolicies =
new HashMap<IBinder, AudioPolicyProxy>();
+ private int mAudioPolicyCounter = 0; // always accessed synchronized on mAudioPolicies
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 2d8042cb367e..317cc212c463 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -207,6 +207,6 @@ interface IAudioService {
boolean isHdmiSystemAudioSupported();
- boolean registerAudioPolicy(in AudioPolicyConfig policyConfig, IBinder cb);
+ String registerAudioPolicy(in AudioPolicyConfig policyConfig, IBinder cb);
oneway void unregisterAudioPolicyAsync(in IBinder cb);
}
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index f7967f1f7d7b..bb5268261d78 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -24,13 +24,14 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * @hide CANDIDATE FOR PUBLIC API
+ * @hide
*/
public class AudioMix {
private AudioMixingRule mRule;
private AudioFormat mFormat;
private int mRouteFlags;
+ private String mRegistrationId;
/**
* All parameters are guaranteed valid through the Builder.
@@ -39,6 +40,7 @@ public class AudioMix {
mRule = rule;
mFormat = format;
mRouteFlags = routeFlags;
+ mRegistrationId = null;
}
/**
@@ -65,6 +67,15 @@ public class AudioMix {
return mRule;
}
+ void setRegistration(String regId) {
+ mRegistrationId = regId;
+ }
+
+ /** @hide */
+ public String getRegistration() {
+ return mRegistrationId;
+ }
+
/** @hide */
@IntDef(flag = true,
value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index ced78817a992..2e06a807624f 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -23,7 +23,7 @@ import java.util.Iterator;
/**
- * @hide CANDIDATE FOR PUBLIC API
+ * @hide
*
* Here's an example of creating a mixing rule for all media playback:
* <pre>
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 314eb887d1d1..255d828b4bfa 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -17,18 +17,26 @@
package android.media.audiopolicy;
import android.annotation.IntDef;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioSystem;
+import android.media.AudioTrack;
+import android.media.MediaRecorder;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
+import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
- * @hide CANDIDATE FOR PUBLIC API
+ * @hide
* AudioPolicy provides access to the management of audio routing and audio focus.
*/
public class AudioPolicy {
@@ -49,11 +57,13 @@ public class AudioPolicy {
public static final int POLICY_STATUS_REGISTERED = 2;
private int mStatus;
- private AudioPolicyStatusListener mStatusListener = null;
+ private String mRegistrationId;
+ private AudioPolicyStatusListener mStatusListener;
private final IBinder mToken = new Binder();
/** @hide */
public IBinder token() { return mToken; }
+ private Context mContext;
private AudioPolicyConfig mConfig;
/** @hide */
@@ -62,13 +72,14 @@ public class AudioPolicy {
/**
* The parameter is guaranteed non-null through the Builder
*/
- private AudioPolicy(AudioPolicyConfig config) {
+ private AudioPolicy(AudioPolicyConfig config, Context context) {
mConfig = config;
if (mConfig.mMixes.isEmpty()) {
mStatus = POLICY_STATUS_INVALID;
} else {
mStatus = POLICY_STATUS_UNREGISTERED;
}
+ mContext = context;
}
/**
@@ -76,12 +87,15 @@ public class AudioPolicy {
*/
public static class Builder {
private ArrayList<AudioMix> mMixes;
+ private Context mContext;
/**
* Constructs a new Builder with no audio mixes.
+ * @param context the context for the policy
*/
- public Builder() {
+ public Builder(Context context) {
mMixes = new ArrayList<AudioMix>();
+ mContext = context;
}
/**
@@ -99,10 +113,115 @@ public class AudioPolicy {
}
public AudioPolicy build() {
- return new AudioPolicy(new AudioPolicyConfig(mMixes));
+ return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext);
}
}
+ /** @hide */
+ public void setRegistration(String regId) {
+ mRegistrationId = regId;
+ mConfig.setRegistration(regId);
+ }
+
+ private boolean policyReadyToUse() {
+ if (mContext == null) {
+ Log.e(TAG, "Cannot use AudioPolicy without context");
+ return false;
+ }
+ if (mRegistrationId == null) {
+ Log.e(TAG, "Cannot use unregistered AudioPolicy");
+ return false;
+ }
+ if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
+ Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
+ + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING");
+ return false;
+ }
+ return true;
+ }
+
+ private void checkMixReadyToUse(AudioMix mix, boolean forTrack)
+ throws IllegalArgumentException{
+ if (mix == null) {
+ String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation"
+ : "Invalid null AudioMix for AudioRecord creation";
+ throw new IllegalArgumentException(msg);
+ }
+ if (!mConfig.mMixes.contains(mix)) {
+ throw new IllegalArgumentException("Invalid mix: not part of this policy");
+ }
+ if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK)
+ {
+ throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
+ }
+ }
+
+ /**
+ * @hide
+ * 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
+ * streams that fed the given mixer.
+ * @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.
+ * @return a new {@link AudioRecord} instance whose data format is the one defined in the
+ * {@link AudioMix}, or null if this policy was not successfully registered
+ * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
+ * @throws IllegalArgumentException
+ */
+ public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
+ if (!policyReadyToUse()) {
+ Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
+ return null;
+ }
+ checkMixReadyToUse(mix, false/*not for an AudioTrack*/);
+ // create the AudioRecord, configured for loop back, using the same format as the mix
+ AudioRecord ar = new AudioRecord(
+ new AudioAttributes.Builder()
+ .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
+ .addTag(mix.getRegistration())
+ .build(),
+ mix.getFormat(),
+ AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
+ // using stereo for buffer size to avoid the current poor support for masks
+ AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()),
+ AudioManager.AUDIO_SESSION_ID_GENERATE
+ );
+ return ar;
+ }
+
+ /**
+ * @hide
+ * 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
+ * to be recorded through the recording APIs.
+ * @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.
+ * @returna new {@link AudioTrack} instance whose data format is the one defined in the
+ * {@link AudioMix}, or null if this policy was not successfully registered
+ * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
+ * @throws IllegalArgumentException
+ */
+ public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
+ if (!policyReadyToUse()) {
+ Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
+ return null;
+ }
+ checkMixReadyToUse(mix, true/*for an AudioTrack*/);
+ // create the AudioTrack, configured for loop back, using the same format as the mix
+ AudioTrack at = new AudioTrack(
+ new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
+ .addTag(mix.getRegistration())
+ .build(),
+ mix.getFormat(),
+ AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
+ mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
+ AudioTrack.MODE_STREAM,
+ AudioManager.AUDIO_SESSION_ID_GENERATE
+ );
+ return at;
+ }
public int getStatus() {
return mStatus;
@@ -118,10 +237,9 @@ public class AudioPolicy {
}
/** @hide */
- @Override
- public String toString () {
+ public String toLogFriendlyString() {
String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
- textDump += "config=" + mConfig.toString();
+ textDump += "config=" + mConfig.toLogFriendlyString();
return (textDump);
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 2fc6d58b2e61..a9a4175916ce 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -36,7 +36,13 @@ public class AudioPolicyConfig implements Parcelable {
private static final String TAG = "AudioPolicyConfig";
- ArrayList<AudioMix> mMixes;
+ protected ArrayList<AudioMix> mMixes;
+
+ protected String mRegistrationId = null;
+
+ protected AudioPolicyConfig(AudioPolicyConfig conf) {
+ mMixes = conf.mMixes;
+ }
AudioPolicyConfig(ArrayList<AudioMix> mixes) {
mMixes = mixes;
@@ -117,7 +123,6 @@ public class AudioPolicyConfig implements Parcelable {
}
}
- /** @hide */
public static final Parcelable.Creator<AudioPolicyConfig> CREATOR
= new Parcelable.Creator<AudioPolicyConfig>() {
/**
@@ -133,9 +138,7 @@ public class AudioPolicyConfig implements Parcelable {
}
};
- /** @hide */
- @Override
- public String toString () {
+ public String toLogFriendlyString () {
String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
textDump += mMixes.size() + " AudioMix:\n";
for(AudioMix mix : mMixes) {
@@ -166,4 +169,13 @@ public class AudioPolicyConfig implements Parcelable {
}
return textDump;
}
+
+ public void setRegistration(String regId) {
+ mRegistrationId = regId;
+ int mixIndex = 0;
+ for (AudioMix mix : mMixes) {
+ mix.setRegistration(mRegistrationId + "mix:" + mixIndex++);
+ }
+ }
+
}