diff options
| -rw-r--r-- | media/java/android/media/AudioAttributes.java | 7 | ||||
| -rw-r--r-- | media/java/android/media/AudioManager.java | 6 | ||||
| -rw-r--r-- | media/java/android/media/AudioService.java | 59 | ||||
| -rw-r--r-- | media/java/android/media/IAudioService.aidl | 2 | ||||
| -rw-r--r-- | media/java/android/media/audiopolicy/AudioMix.java | 13 | ||||
| -rw-r--r-- | media/java/android/media/audiopolicy/AudioMixingRule.java | 2 | ||||
| -rw-r--r-- | media/java/android/media/audiopolicy/AudioPolicy.java | 134 | ||||
| -rw-r--r-- | media/java/android/media/audiopolicy/AudioPolicyConfig.java | 22 |
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++); + } + } + } |