AudioPolicy.java revision 1b3541d5eedb332ea01066b4a78a2d06d5304044
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.annotation.NonNull; 21import android.annotation.SystemApi; 22import android.content.Context; 23import android.content.pm.PackageManager; 24import android.media.AudioAttributes; 25import android.media.AudioFormat; 26import android.media.AudioManager; 27import android.media.AudioRecord; 28import android.media.AudioTrack; 29import android.media.MediaRecorder; 30import android.os.Binder; 31import android.os.Handler; 32import android.os.IBinder; 33import android.os.Looper; 34import android.os.Message; 35import android.util.Log; 36import android.util.Slog; 37 38import java.lang.annotation.Retention; 39import java.lang.annotation.RetentionPolicy; 40import java.util.ArrayList; 41 42/** 43 * @hide 44 * AudioPolicy provides access to the management of audio routing and audio focus. 45 */ 46@SystemApi 47public class AudioPolicy { 48 49 private static final String TAG = "AudioPolicy"; 50 51 /** 52 * The status of an audio policy that is valid but cannot be used because it is not registered. 53 */ 54 @SystemApi 55 public static final int POLICY_STATUS_UNREGISTERED = 1; 56 /** 57 * The status of an audio policy that is valid, successfully registered and thus active. 58 */ 59 @SystemApi 60 public static final int POLICY_STATUS_REGISTERED = 2; 61 62 private int mStatus; 63 private String mRegistrationId; 64 private AudioPolicyStatusListener mStatusListener; 65 66 private final IBinder mToken = new Binder(); 67 /** @hide */ 68 public IBinder token() { return mToken; } 69 private Context mContext; 70 71 private AudioPolicyConfig mConfig; 72 /** @hide */ 73 public AudioPolicyConfig getConfig() { return mConfig; } 74 75 /** 76 * The parameter is guaranteed non-null through the Builder 77 */ 78 private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper) { 79 mConfig = config; 80 mStatus = POLICY_STATUS_UNREGISTERED; 81 mContext = context; 82 if (looper == null) { 83 looper = Looper.getMainLooper(); 84 } 85 if (looper != null) { 86 mEventHandler = new EventHandler(this, looper); 87 } else { 88 mEventHandler = null; 89 Log.e(TAG, "No event handler due to looper without a thread"); 90 } 91 } 92 93 /** 94 * Builder class for {@link AudioPolicy} objects 95 */ 96 @SystemApi 97 public static class Builder { 98 private ArrayList<AudioMix> mMixes; 99 private Context mContext; 100 private Looper mLooper; 101 102 /** 103 * Constructs a new Builder with no audio mixes. 104 * @param context the context for the policy 105 */ 106 public Builder(Context context) { 107 mMixes = new ArrayList<AudioMix>(); 108 mContext = context; 109 } 110 111 /** 112 * Add an {@link AudioMix} to be part of the audio policy being built. 113 * @param mix a non-null {@link AudioMix} to be part of the audio policy. 114 * @return the same Builder instance. 115 * @throws IllegalArgumentException 116 */ 117 public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException { 118 if (mix == null) { 119 throw new IllegalArgumentException("Illegal null AudioMix argument"); 120 } 121 mMixes.add(mix); 122 return this; 123 } 124 125 /** 126 * Sets the {@link Looper} on which to run the event loop. 127 * @param looper a non-null specific Looper. 128 * @return the same Builder instance. 129 * @throws IllegalArgumentException 130 */ 131 public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException { 132 if (looper == null) { 133 throw new IllegalArgumentException("Illegal null Looper argument"); 134 } 135 mLooper = looper; 136 return this; 137 } 138 139 public AudioPolicy build() { 140 return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper); 141 } 142 } 143 144 public void setRegistration(String regId) { 145 mRegistrationId = regId; 146 mConfig.setRegistration(regId); 147 if (regId != null) { 148 mStatus = POLICY_STATUS_REGISTERED; 149 } else { 150 mStatus = POLICY_STATUS_UNREGISTERED; 151 } 152 sendMsg(mEventHandler, MSG_POLICY_STATUS_CHANGE); 153 } 154 155 private boolean policyReadyToUse() { 156 if (mStatus != POLICY_STATUS_REGISTERED) { 157 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 158 return false; 159 } 160 if (mContext == null) { 161 Log.e(TAG, "Cannot use AudioPolicy without context"); 162 return false; 163 } 164 if (mRegistrationId == null) { 165 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 166 return false; 167 } 168 if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( 169 android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { 170 Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid " 171 + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING"); 172 return false; 173 } 174 return true; 175 } 176 177 private void checkMixReadyToUse(AudioMix mix, boolean forTrack) 178 throws IllegalArgumentException{ 179 if (mix == null) { 180 String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation" 181 : "Invalid null AudioMix for AudioRecord creation"; 182 throw new IllegalArgumentException(msg); 183 } 184 if (!mConfig.mMixes.contains(mix)) { 185 throw new IllegalArgumentException("Invalid mix: not part of this policy"); 186 } 187 if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK) 188 { 189 throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back"); 190 } 191 if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) { 192 throw new IllegalArgumentException( 193 "Invalid AudioMix: not defined for being a recording source"); 194 } 195 if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) { 196 throw new IllegalArgumentException( 197 "Invalid AudioMix: not defined for capturing playback"); 198 } 199 } 200 201 /** 202 * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}. 203 * Audio buffers recorded through the created instance will contain the mix of the audio 204 * streams that fed the given mixer. 205 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 206 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 207 * @return a new {@link AudioRecord} instance whose data format is the one defined in the 208 * {@link AudioMix}, or null if this policy was not successfully registered 209 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 210 * @throws IllegalArgumentException 211 */ 212 @SystemApi 213 public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException { 214 if (!policyReadyToUse()) { 215 Log.e(TAG, "Cannot create AudioRecord sink for AudioMix"); 216 return null; 217 } 218 checkMixReadyToUse(mix, false/*not for an AudioTrack*/); 219 // create an AudioFormat from the mix format compatible with recording, as the mix 220 // was defined for playback 221 AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat()) 222 .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask( 223 mix.getFormat().getChannelMask())) 224 .build(); 225 // create the AudioRecord, configured for loop back, using the same format as the mix 226 AudioRecord ar = new AudioRecord( 227 new AudioAttributes.Builder() 228 .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX) 229 .addTag(addressForTag(mix)) 230 .build(), 231 mixFormat, 232 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(), 233 // using stereo for buffer size to avoid the current poor support for masks 234 AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()), 235 AudioManager.AUDIO_SESSION_ID_GENERATE 236 ); 237 return ar; 238 } 239 240 /** 241 * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}. 242 * Audio buffers played through the created instance will be sent to the given mix 243 * to be recorded through the recording APIs. 244 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 245 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 246 * @return a new {@link AudioTrack} instance whose data format is the one defined in the 247 * {@link AudioMix}, or null if this policy was not successfully registered 248 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 249 * @throws IllegalArgumentException 250 */ 251 @SystemApi 252 public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException { 253 if (!policyReadyToUse()) { 254 Log.e(TAG, "Cannot create AudioTrack source for AudioMix"); 255 return null; 256 } 257 checkMixReadyToUse(mix, true/*for an AudioTrack*/); 258 // create the AudioTrack, configured for loop back, using the same format as the mix 259 AudioTrack at = new AudioTrack( 260 new AudioAttributes.Builder() 261 .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE) 262 .addTag(addressForTag(mix)) 263 .build(), 264 mix.getFormat(), 265 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(), 266 mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()), 267 AudioTrack.MODE_STREAM, 268 AudioManager.AUDIO_SESSION_ID_GENERATE 269 ); 270 return at; 271 } 272 273 @SystemApi 274 public int getStatus() { 275 return mStatus; 276 } 277 278 @SystemApi 279 public static abstract class AudioPolicyStatusListener { 280 public void onStatusChange() {} 281 public void onMixStateUpdate(AudioMix mix) {} 282 } 283 284 @SystemApi 285 synchronized public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) { 286 mStatusListener = l; 287 } 288 289 synchronized private void onPolicyStatusChange() { 290 if (mStatusListener == null) { 291 return; 292 } 293 mStatusListener.onStatusChange(); 294 } 295 296 //================================================== 297 // Event handling 298 private final EventHandler mEventHandler; 299 private final static int MSG_POLICY_STATUS_CHANGE = 0; 300 301 private class EventHandler extends Handler { 302 public EventHandler(AudioPolicy ap, Looper looper) { 303 super(looper); 304 } 305 306 @Override 307 public void handleMessage(Message msg) { 308 switch(msg.what) { 309 case MSG_POLICY_STATUS_CHANGE: 310 onPolicyStatusChange(); 311 break; 312 default: 313 Log.e(TAG, "Unknown event " + msg.what); 314 } 315 } 316 } 317 318 //========================================================== 319 // Utils 320 private static String addressForTag(AudioMix mix) { 321 return "addr=" + mix.getRegistration(); 322 } 323 324 private static void sendMsg(Handler handler, int msg) { 325 if (handler != null) { 326 handler.sendEmptyMessage(msg); 327 } 328 } 329 330 public String toLogFriendlyString() { 331 String textDump = new String("android.media.audiopolicy.AudioPolicy:\n"); 332 textDump += "config=" + mConfig.toLogFriendlyString(); 333 return (textDump); 334 } 335 336 /** @hide */ 337 @IntDef({ 338 POLICY_STATUS_REGISTERED, 339 POLICY_STATUS_UNREGISTERED 340 }) 341 @Retention(RetentionPolicy.SOURCE) 342 public @interface PolicyStatus {} 343} 344