Merge "More AudioPolicy registration" into lmp-mr1-dev

This commit is contained in:
Jean-Michel Trivi
2014-10-31 16:32:52 +00:00
committed by Android (Google) Code Review
8 changed files with 218 additions and 27 deletions

View File

@@ -161,6 +161,12 @@ public final class AudioAttributes implements Parcelable {
* Usage value to use when the usage is for game audio. * Usage value to use when the usage is for game audio.
*/ */
public final static int USAGE_GAME = 14; 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. * 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_NAVIGATION_GUIDANCE:
case USAGE_ASSISTANCE_SONIFICATION: case USAGE_ASSISTANCE_SONIFICATION:
case USAGE_GAME: case USAGE_GAME:
case USAGE_VIRTUAL_SOURCE:
mUsage = usage; mUsage = usage;
break; break;
default: default:

View File

@@ -2663,9 +2663,13 @@ public class AudioManager {
} }
IAudioService service = getService(); IAudioService service = getService();
try { try {
if (!service.registerAudioPolicy(policy.getConfig(), policy.token())) { String regId = service.registerAudioPolicy(policy.getConfig(), policy.token());
if (regId == null) {
return ERROR; return ERROR;
} else {
policy.setRegistration(regId);
} }
// successful registration
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Dead object in registerAudioPolicyAsync()", e); Log.e(TAG, "Dead object in registerAudioPolicyAsync()", e);
return ERROR; return ERROR;

View File

@@ -48,6 +48,7 @@ import android.hardware.hdmi.HdmiTvClient;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnErrorListener;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicyConfig; import android.media.audiopolicy.AudioPolicyConfig;
import android.media.session.MediaSessionLegacyHelper; import android.media.session.MediaSessionLegacyHelper;
import android.os.Binder; import android.os.Binder;
@@ -118,6 +119,10 @@ public class AudioService extends IAudioService.Stub {
/** Debug audio mode */ /** Debug audio mode */
protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG); 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 */ /** Debug volumes */
protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG); 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 // 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); //Log.v(TAG, "registerAudioPolicy for " + cb + " got policy:" + policyConfig);
String regId = null;
boolean hasPermissionForPolicy = boolean hasPermissionForPolicy =
(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_AUDIO_ROUTING)); android.Manifest.permission.MODIFY_AUDIO_ROUTING));
if (!hasPermissionForPolicy) { if (!hasPermissionForPolicy) {
Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid " Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid "
+ Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING"); + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
return false; return null;
} }
synchronized (mAudioPolicies) { synchronized (mAudioPolicies) {
AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
try { try {
AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
cb.linkToDeath(app, 0/*flags*/); cb.linkToDeath(app, 0/*flags*/);
regId = app.connectMixes();
mAudioPolicies.put(cb, app); mAudioPolicies.put(cb, app);
} catch (RemoteException e) { } catch (RemoteException e) {
// audio policy owner has already died! // audio policy owner has already died!
Slog.w(TAG, "Audio policy registration failed, could not link to " + cb + Slog.w(TAG, "Audio policy registration failed, could not link to " + cb +
" binder death", e); " binder death", e);
return false; return null;
} }
} }
// TODO implement registration with native audio policy (including permission check) return regId;
return true;
} }
public void unregisterAudioPolicyAsync(IBinder cb) { public void unregisterAudioPolicyAsync(IBinder cb) {
synchronized (mAudioPolicies) { synchronized (mAudioPolicies) {
AudioPolicyProxy app = mAudioPolicies.remove(cb); AudioPolicyProxy app = mAudioPolicies.remove(cb);
@@ -5668,27 +5675,59 @@ public class AudioService extends IAudioService.Stub {
} else { } else {
cb.unlinkToDeath(app, 0/*flags*/); 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"; private static final String TAG = "AudioPolicyProxy";
AudioPolicyConfig mConfig; AudioPolicyConfig mConfig;
IBinder mToken; IBinder mToken;
AudioPolicyProxy(AudioPolicyConfig config, IBinder token) { AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
mConfig = config; super(config);
setRegistration(new String(config.toString() + ":ap:" + mAudioPolicyCounter++));
mToken = token; mToken = token;
} }
public void binderDied() { public void binderDied() {
synchronized (mAudioPolicies) { synchronized (mAudioPolicies) {
Log.v(TAG, "audio policy " + mToken + " died"); Log.i(TAG, "audio policy " + mToken + " died");
mAudioPolicies.remove(mToken); 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 = private HashMap<IBinder, AudioPolicyProxy> mAudioPolicies =
new HashMap<IBinder, AudioPolicyProxy>(); new HashMap<IBinder, AudioPolicyProxy>();
private int mAudioPolicyCounter = 0; // always accessed synchronized on mAudioPolicies
} }

View File

@@ -207,6 +207,6 @@ interface IAudioService {
boolean isHdmiSystemAudioSupported(); boolean isHdmiSystemAudioSupported();
boolean registerAudioPolicy(in AudioPolicyConfig policyConfig, IBinder cb); String registerAudioPolicy(in AudioPolicyConfig policyConfig, IBinder cb);
oneway void unregisterAudioPolicyAsync(in IBinder cb); oneway void unregisterAudioPolicyAsync(in IBinder cb);
} }

View File

@@ -24,13 +24,14 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
/** /**
* @hide CANDIDATE FOR PUBLIC API * @hide
*/ */
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;
/** /**
* All parameters are guaranteed valid through the Builder. * All parameters are guaranteed valid through the Builder.
@@ -39,6 +40,7 @@ public class AudioMix {
mRule = rule; mRule = rule;
mFormat = format; mFormat = format;
mRouteFlags = routeFlags; mRouteFlags = routeFlags;
mRegistrationId = null;
} }
/** /**
@@ -65,6 +67,15 @@ public class AudioMix {
return mRule; return mRule;
} }
void setRegistration(String regId) {
mRegistrationId = regId;
}
/** @hide */
public String getRegistration() {
return mRegistrationId;
}
/** @hide */ /** @hide */
@IntDef(flag = true, @IntDef(flag = true,
value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } ) value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )

View File

@@ -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: * Here's an example of creating a mixing rule for all media playback:
* <pre> * <pre>

View File

@@ -17,18 +17,26 @@
package android.media.audiopolicy; package android.media.audiopolicy;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.content.Context;
import android.content.pm.PackageManager;
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.AudioSystem;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import android.util.Log;
import android.util.Slog;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* @hide CANDIDATE FOR PUBLIC API * @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.
*/ */
public class AudioPolicy { public class AudioPolicy {
@@ -49,11 +57,13 @@ public class AudioPolicy {
public static final int POLICY_STATUS_REGISTERED = 2; public static final int POLICY_STATUS_REGISTERED = 2;
private int mStatus; private int mStatus;
private AudioPolicyStatusListener mStatusListener = null; private String mRegistrationId;
private AudioPolicyStatusListener mStatusListener;
private final IBinder mToken = new Binder(); private final IBinder mToken = new Binder();
/** @hide */ /** @hide */
public IBinder token() { return mToken; } public IBinder token() { return mToken; }
private Context mContext;
private AudioPolicyConfig mConfig; private AudioPolicyConfig mConfig;
/** @hide */ /** @hide */
@@ -62,13 +72,14 @@ 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) { private AudioPolicy(AudioPolicyConfig config, Context context) {
mConfig = config; mConfig = config;
if (mConfig.mMixes.isEmpty()) { if (mConfig.mMixes.isEmpty()) {
mStatus = POLICY_STATUS_INVALID; mStatus = POLICY_STATUS_INVALID;
} else { } else {
mStatus = POLICY_STATUS_UNREGISTERED; mStatus = POLICY_STATUS_UNREGISTERED;
} }
mContext = context;
} }
/** /**
@@ -76,12 +87,15 @@ public class AudioPolicy {
*/ */
public static class Builder { public static class Builder {
private ArrayList<AudioMix> mMixes; private ArrayList<AudioMix> mMixes;
private Context mContext;
/** /**
* Constructs a new Builder with no audio mixes. * 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>(); mMixes = new ArrayList<AudioMix>();
mContext = context;
} }
/** /**
@@ -99,10 +113,115 @@ public class AudioPolicy {
} }
public AudioPolicy build() { 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() { public int getStatus() {
return mStatus; return mStatus;
@@ -118,10 +237,9 @@ public class AudioPolicy {
} }
/** @hide */ /** @hide */
@Override public String toLogFriendlyString() {
public String toString () {
String textDump = new String("android.media.audiopolicy.AudioPolicy:\n"); String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
textDump += "config=" + mConfig.toString(); textDump += "config=" + mConfig.toLogFriendlyString();
return (textDump); return (textDump);
} }

View File

@@ -36,7 +36,13 @@ public class AudioPolicyConfig implements Parcelable {
private static final String TAG = "AudioPolicyConfig"; 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) { AudioPolicyConfig(ArrayList<AudioMix> mixes) {
mMixes = mixes; mMixes = mixes;
@@ -117,7 +123,6 @@ public class AudioPolicyConfig implements Parcelable {
} }
} }
/** @hide */
public static final Parcelable.Creator<AudioPolicyConfig> CREATOR public static final Parcelable.Creator<AudioPolicyConfig> CREATOR
= new Parcelable.Creator<AudioPolicyConfig>() { = new Parcelable.Creator<AudioPolicyConfig>() {
/** /**
@@ -133,9 +138,7 @@ public class AudioPolicyConfig implements Parcelable {
} }
}; };
/** @hide */ public String toLogFriendlyString () {
@Override
public String toString () {
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:\n";
for(AudioMix mix : mMixes) { for(AudioMix mix : mMixes) {
@@ -166,4 +169,13 @@ public class AudioPolicyConfig implements Parcelable {
} }
return textDump; return textDump;
} }
public void setRegistration(String regId) {
mRegistrationId = regId;
int mixIndex = 0;
for (AudioMix mix : mMixes) {
mix.setRegistration(mRegistrationId + "mix:" + mixIndex++);
}
}
} }