AudioPolicy.java revision 8fdb0d4defb6ee2ca8057d3442ead36b408b6c17
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.media.audiopolicy; 18 19import android.annotation.IntDef; 20import android.content.Context; 21import android.content.pm.PackageManager; 22import android.media.AudioAttributes; 23import android.media.AudioFormat; 24import android.media.AudioManager; 25import android.media.AudioRecord; 26import android.media.AudioSystem; 27import android.media.AudioTrack; 28import android.media.MediaRecorder; 29import android.os.Binder; 30import android.os.IBinder; 31import android.util.Log; 32import android.util.Slog; 33 34import java.lang.annotation.Retention; 35import java.lang.annotation.RetentionPolicy; 36import java.util.ArrayList; 37 38/** 39 * @hide 40 * AudioPolicy provides access to the management of audio routing and audio focus. 41 */ 42public class AudioPolicy { 43 44 private static final String TAG = "AudioPolicy"; 45 46 /** 47 * The status of an audio policy that cannot be used because it is invalid. 48 */ 49 public static final int POLICY_STATUS_INVALID = 0; 50 /** 51 * The status of an audio policy that is valid but cannot be used because it is not registered. 52 */ 53 public static final int POLICY_STATUS_UNREGISTERED = 1; 54 /** 55 * The status of an audio policy that is valid, successfully registered and thus active. 56 */ 57 public static final int POLICY_STATUS_REGISTERED = 2; 58 59 private int mStatus; 60 private String mRegistrationId; 61 private AudioPolicyStatusListener mStatusListener; 62 63 private final IBinder mToken = new Binder(); 64 /** @hide */ 65 public IBinder token() { return mToken; } 66 private Context mContext; 67 68 private AudioPolicyConfig mConfig; 69 /** @hide */ 70 public AudioPolicyConfig getConfig() { return mConfig; } 71 72 /** 73 * The parameter is guaranteed non-null through the Builder 74 */ 75 private AudioPolicy(AudioPolicyConfig config, Context context) { 76 mConfig = config; 77 if (mConfig.mMixes.isEmpty()) { 78 mStatus = POLICY_STATUS_INVALID; 79 } else { 80 mStatus = POLICY_STATUS_UNREGISTERED; 81 } 82 mContext = context; 83 } 84 85 /** 86 * Builder class for {@link AudioPolicy} objects 87 */ 88 public static class Builder { 89 private ArrayList<AudioMix> mMixes; 90 private Context mContext; 91 92 /** 93 * Constructs a new Builder with no audio mixes. 94 * @param context the context for the policy 95 */ 96 public Builder(Context context) { 97 mMixes = new ArrayList<AudioMix>(); 98 mContext = context; 99 } 100 101 /** 102 * Add an {@link AudioMix} to be part of the audio policy being built. 103 * @param mix a non-null {@link AudioMix} to be part of the audio policy. 104 * @return the same Builder instance. 105 * @throws IllegalArgumentException 106 */ 107 public Builder addMix(AudioMix mix) throws IllegalArgumentException { 108 if (mix == null) { 109 throw new IllegalArgumentException("Illegal null AudioMix argument"); 110 } 111 mMixes.add(mix); 112 return this; 113 } 114 115 public AudioPolicy build() { 116 return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext); 117 } 118 } 119 120 /** @hide */ 121 public void setRegistration(String regId) { 122 mRegistrationId = regId; 123 mConfig.setRegistration(regId); 124 } 125 126 private boolean policyReadyToUse() { 127 if (mContext == null) { 128 Log.e(TAG, "Cannot use AudioPolicy without context"); 129 return false; 130 } 131 if (mRegistrationId == null) { 132 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 133 return false; 134 } 135 if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( 136 android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { 137 Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid " 138 + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING"); 139 return false; 140 } 141 return true; 142 } 143 144 private void checkMixReadyToUse(AudioMix mix, boolean forTrack) 145 throws IllegalArgumentException{ 146 if (mix == null) { 147 String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation" 148 : "Invalid null AudioMix for AudioRecord creation"; 149 throw new IllegalArgumentException(msg); 150 } 151 if (!mConfig.mMixes.contains(mix)) { 152 throw new IllegalArgumentException("Invalid mix: not part of this policy"); 153 } 154 if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK) 155 { 156 throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back"); 157 } 158 } 159 160 /** 161 * @hide 162 * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}. 163 * Audio buffers recorded through the created instance will contain the mix of the audio 164 * streams that fed the given mixer. 165 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 166 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 167 * @return a new {@link AudioRecord} instance whose data format is the one defined in the 168 * {@link AudioMix}, or null if this policy was not successfully registered 169 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 170 * @throws IllegalArgumentException 171 */ 172 public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException { 173 if (!policyReadyToUse()) { 174 Log.e(TAG, "Cannot create AudioRecord sink for AudioMix"); 175 return null; 176 } 177 checkMixReadyToUse(mix, false/*not for an AudioTrack*/); 178 // create the AudioRecord, configured for loop back, using the same format as the mix 179 AudioRecord ar = new AudioRecord( 180 new AudioAttributes.Builder() 181 .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX) 182 .addTag(mix.getRegistration()) 183 .build(), 184 mix.getFormat(), 185 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(), 186 // using stereo for buffer size to avoid the current poor support for masks 187 AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()), 188 AudioManager.AUDIO_SESSION_ID_GENERATE 189 ); 190 return ar; 191 } 192 193 /** 194 * @hide 195 * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}. 196 * Audio buffers played through the created instance will be sent to the given mix 197 * to be recorded through the recording APIs. 198 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 199 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 200 * @returna new {@link AudioTrack} instance whose data format is the one defined in the 201 * {@link AudioMix}, or null if this policy was not successfully registered 202 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 203 * @throws IllegalArgumentException 204 */ 205 public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException { 206 if (!policyReadyToUse()) { 207 Log.e(TAG, "Cannot create AudioTrack source for AudioMix"); 208 return null; 209 } 210 checkMixReadyToUse(mix, true/*for an AudioTrack*/); 211 // create the AudioTrack, configured for loop back, using the same format as the mix 212 AudioTrack at = new AudioTrack( 213 new AudioAttributes.Builder() 214 .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE) 215 .addTag(mix.getRegistration()) 216 .build(), 217 mix.getFormat(), 218 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(), 219 mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()), 220 AudioTrack.MODE_STREAM, 221 AudioManager.AUDIO_SESSION_ID_GENERATE 222 ); 223 return at; 224 } 225 226 public int getStatus() { 227 return mStatus; 228 } 229 230 public static abstract class AudioPolicyStatusListener { 231 void onStatusChange() {} 232 void onMixStateUpdate(AudioMix mix) {} 233 } 234 235 void setStatusListener(AudioPolicyStatusListener l) { 236 mStatusListener = l; 237 } 238 239 /** @hide */ 240 public String toLogFriendlyString() { 241 String textDump = new String("android.media.audiopolicy.AudioPolicy:\n"); 242 textDump += "config=" + mConfig.toLogFriendlyString(); 243 return (textDump); 244 } 245 246 /** @hide */ 247 @IntDef({ 248 POLICY_STATUS_INVALID, 249 POLICY_STATUS_REGISTERED, 250 POLICY_STATUS_UNREGISTERED 251 }) 252 @Retention(RetentionPolicy.SOURCE) 253 public @interface PolicyStatus {} 254} 255