AudioPolicy.java revision 0212be5150fb9fb3c340f3c7e51f6126372cc6f9
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.AudioFocusInfo; 26import android.media.AudioFormat; 27import android.media.AudioManager; 28import android.media.AudioRecord; 29import android.media.AudioTrack; 30import android.media.IAudioService; 31import android.media.MediaRecorder; 32import android.os.Binder; 33import android.os.Handler; 34import android.os.IBinder; 35import android.os.Looper; 36import android.os.Message; 37import android.os.RemoteException; 38import android.os.ServiceManager; 39import android.util.Log; 40import android.util.Slog; 41 42import java.lang.annotation.Retention; 43import java.lang.annotation.RetentionPolicy; 44import java.util.ArrayList; 45 46/** 47 * @hide 48 * AudioPolicy provides access to the management of audio routing and audio focus. 49 */ 50@SystemApi 51public class AudioPolicy { 52 53 private static final String TAG = "AudioPolicy"; 54 private static final boolean DEBUG = false; 55 private final Object mLock = new Object(); 56 57 /** 58 * The status of an audio policy that is valid but cannot be used because it is not registered. 59 */ 60 @SystemApi 61 public static final int POLICY_STATUS_UNREGISTERED = 1; 62 /** 63 * The status of an audio policy that is valid, successfully registered and thus active. 64 */ 65 @SystemApi 66 public static final int POLICY_STATUS_REGISTERED = 2; 67 68 private int mStatus; 69 private String mRegistrationId; 70 private AudioPolicyStatusListener mStatusListener; 71 72 /** 73 * The behavior of a policy with regards to audio focus where it relies on the application 74 * to do the ducking, the is the legacy and default behavior. 75 */ 76 @SystemApi 77 public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; 78 public static final int FOCUS_POLICY_DUCKING_DEFAULT = FOCUS_POLICY_DUCKING_IN_APP; 79 /** 80 * The behavior of a policy with regards to audio focus where it handles ducking instead 81 * of the application losing focus and being signaled it can duck (as communicated by 82 * {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}). 83 * <br>Can only be used after having set a listener with 84 * {@link AudioPolicy#setAudioPolicyFocusListener(AudioPolicyFocusListener)}. 85 */ 86 @SystemApi 87 public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; 88 89 private AudioPolicyFocusListener mFocusListener; 90 91 private Context mContext; 92 93 private AudioPolicyConfig mConfig; 94 95 /** @hide */ 96 public AudioPolicyConfig getConfig() { return mConfig; } 97 /** @hide */ 98 public boolean hasFocusListener() { return mFocusListener != null; } 99 100 /** 101 * The parameter is guaranteed non-null through the Builder 102 */ 103 private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, 104 AudioPolicyFocusListener fl, AudioPolicyStatusListener sl) { 105 mConfig = config; 106 mStatus = POLICY_STATUS_UNREGISTERED; 107 mContext = context; 108 if (looper == null) { 109 looper = Looper.getMainLooper(); 110 } 111 if (looper != null) { 112 mEventHandler = new EventHandler(this, looper); 113 } else { 114 mEventHandler = null; 115 Log.e(TAG, "No event handler due to looper without a thread"); 116 } 117 mFocusListener = fl; 118 mStatusListener = sl; 119 } 120 121 /** 122 * Builder class for {@link AudioPolicy} objects 123 */ 124 @SystemApi 125 public static class Builder { 126 private ArrayList<AudioMix> mMixes; 127 private Context mContext; 128 private Looper mLooper; 129 private AudioPolicyFocusListener mFocusListener; 130 private AudioPolicyStatusListener mStatusListener; 131 132 /** 133 * Constructs a new Builder with no audio mixes. 134 * @param context the context for the policy 135 */ 136 @SystemApi 137 public Builder(Context context) { 138 mMixes = new ArrayList<AudioMix>(); 139 mContext = context; 140 } 141 142 /** 143 * Add an {@link AudioMix} to be part of the audio policy being built. 144 * @param mix a non-null {@link AudioMix} to be part of the audio policy. 145 * @return the same Builder instance. 146 * @throws IllegalArgumentException 147 */ 148 @SystemApi 149 public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException { 150 if (mix == null) { 151 throw new IllegalArgumentException("Illegal null AudioMix argument"); 152 } 153 mMixes.add(mix); 154 return this; 155 } 156 157 /** 158 * Sets the {@link Looper} on which to run the event loop. 159 * @param looper a non-null specific Looper. 160 * @return the same Builder instance. 161 * @throws IllegalArgumentException 162 */ 163 @SystemApi 164 public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException { 165 if (looper == null) { 166 throw new IllegalArgumentException("Illegal null Looper argument"); 167 } 168 mLooper = looper; 169 return this; 170 } 171 172 /** 173 * Sets the audio focus listener for the policy. 174 * @param l a {@link AudioPolicy.AudioPolicyFocusListener} 175 */ 176 @SystemApi 177 public void setAudioPolicyFocusListener(AudioPolicyFocusListener l) { 178 mFocusListener = l; 179 } 180 181 /** 182 * Sets the audio policy status listener. 183 * @param l a {@link AudioPolicy.AudioPolicyStatusListener} 184 */ 185 @SystemApi 186 public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) { 187 mStatusListener = l; 188 } 189 190 @SystemApi 191 public AudioPolicy build() { 192 return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper, 193 mFocusListener, mStatusListener); 194 } 195 } 196 197 public void setRegistration(String regId) { 198 synchronized (mLock) { 199 mRegistrationId = regId; 200 mConfig.setRegistration(regId); 201 if (regId != null) { 202 mStatus = POLICY_STATUS_REGISTERED; 203 } else { 204 mStatus = POLICY_STATUS_UNREGISTERED; 205 } 206 } 207 sendMsg(MSG_POLICY_STATUS_CHANGE); 208 } 209 210 private boolean policyReadyToUse() { 211 synchronized (mLock) { 212 if (mStatus != POLICY_STATUS_REGISTERED) { 213 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 214 return false; 215 } 216 if (mContext == null) { 217 Log.e(TAG, "Cannot use AudioPolicy without context"); 218 return false; 219 } 220 if (mRegistrationId == null) { 221 Log.e(TAG, "Cannot use unregistered AudioPolicy"); 222 return false; 223 } 224 } 225 if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( 226 android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { 227 Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid " 228 + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING"); 229 return false; 230 } 231 return true; 232 } 233 234 private void checkMixReadyToUse(AudioMix mix, boolean forTrack) 235 throws IllegalArgumentException{ 236 if (mix == null) { 237 String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation" 238 : "Invalid null AudioMix for AudioRecord creation"; 239 throw new IllegalArgumentException(msg); 240 } 241 if (!mConfig.mMixes.contains(mix)) { 242 throw new IllegalArgumentException("Invalid mix: not part of this policy"); 243 } 244 if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK) 245 { 246 throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back"); 247 } 248 if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) { 249 throw new IllegalArgumentException( 250 "Invalid AudioMix: not defined for being a recording source"); 251 } 252 if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) { 253 throw new IllegalArgumentException( 254 "Invalid AudioMix: not defined for capturing playback"); 255 } 256 } 257 258 /** 259 * Returns the current behavior for audio focus-related ducking. 260 * @return {@link #FOCUS_POLICY_DUCKING_IN_APP} or {@link #FOCUS_POLICY_DUCKING_IN_POLICY} 261 */ 262 @SystemApi 263 public int getFocusDuckingBehavior() { 264 return mConfig.mDuckingPolicy; 265 } 266 267 // Note on implementation: not part of the Builder as there can be only one registered policy 268 // that handles ducking but there can be multiple policies 269 /** 270 * Sets the behavior for audio focus-related ducking. 271 * There must be a focus listener if this policy is to handle ducking. 272 * @param behavior {@link #FOCUS_POLICY_DUCKING_IN_APP} or 273 * {@link #FOCUS_POLICY_DUCKING_IN_POLICY} 274 * @return {@link AudioManager#SUCCESS} or {@link AudioManager#ERROR} (for instance if there 275 * is already an audio policy that handles ducking). 276 * @throws IllegalArgumentException 277 * @throws IllegalStateException 278 */ 279 @SystemApi 280 public int setFocusDuckingBehavior(int behavior) 281 throws IllegalArgumentException, IllegalStateException { 282 if ((behavior != FOCUS_POLICY_DUCKING_IN_APP) 283 && (behavior != FOCUS_POLICY_DUCKING_IN_POLICY)) { 284 throw new IllegalArgumentException("Invalid ducking behavior " + behavior); 285 } 286 synchronized (mLock) { 287 if (mStatus != POLICY_STATUS_REGISTERED) { 288 throw new IllegalStateException( 289 "Cannot change ducking behavior for unregistered policy"); 290 } 291 if ((behavior == FOCUS_POLICY_DUCKING_IN_POLICY) 292 && (mFocusListener == null)) { 293 // there must be a focus listener if the policy handles ducking 294 throw new IllegalStateException( 295 "Cannot handle ducking without an audio focus listener"); 296 } 297 IAudioService service = getService(); 298 try { 299 final int status = service.setFocusPropertiesForPolicy(behavior /*duckingBehavior*/, 300 this.cb()); 301 if (status == AudioManager.SUCCESS) { 302 mConfig.mDuckingPolicy = behavior; 303 } 304 return status; 305 } catch (RemoteException e) { 306 Log.e(TAG, "Dead object in setFocusPropertiesForPolicy for behavior", e); 307 return AudioManager.ERROR; 308 } 309 } 310 } 311 312 /** 313 * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}. 314 * Audio buffers recorded through the created instance will contain the mix of the audio 315 * streams that fed the given mixer. 316 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 317 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 318 * @return a new {@link AudioRecord} instance whose data format is the one defined in the 319 * {@link AudioMix}, or null if this policy was not successfully registered 320 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 321 * @throws IllegalArgumentException 322 */ 323 @SystemApi 324 public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException { 325 if (!policyReadyToUse()) { 326 Log.e(TAG, "Cannot create AudioRecord sink for AudioMix"); 327 return null; 328 } 329 checkMixReadyToUse(mix, false/*not for an AudioTrack*/); 330 // create an AudioFormat from the mix format compatible with recording, as the mix 331 // was defined for playback 332 AudioFormat mixFormat = new AudioFormat.Builder(mix.getFormat()) 333 .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask( 334 mix.getFormat().getChannelMask())) 335 .build(); 336 // create the AudioRecord, configured for loop back, using the same format as the mix 337 AudioRecord ar = new AudioRecord( 338 new AudioAttributes.Builder() 339 .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX) 340 .addTag(addressForTag(mix)) 341 .build(), 342 mixFormat, 343 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(), 344 // using stereo for buffer size to avoid the current poor support for masks 345 AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()), 346 AudioManager.AUDIO_SESSION_ID_GENERATE 347 ); 348 return ar; 349 } 350 351 /** 352 * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}. 353 * Audio buffers played through the created instance will be sent to the given mix 354 * to be recorded through the recording APIs. 355 * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with 356 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy. 357 * @return a new {@link AudioTrack} instance whose data format is the one defined in the 358 * {@link AudioMix}, or null if this policy was not successfully registered 359 * with {@link AudioManager#registerAudioPolicy(AudioPolicy)}. 360 * @throws IllegalArgumentException 361 */ 362 @SystemApi 363 public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException { 364 if (!policyReadyToUse()) { 365 Log.e(TAG, "Cannot create AudioTrack source for AudioMix"); 366 return null; 367 } 368 checkMixReadyToUse(mix, true/*for an AudioTrack*/); 369 // create the AudioTrack, configured for loop back, using the same format as the mix 370 AudioTrack at = new AudioTrack( 371 new AudioAttributes.Builder() 372 .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE) 373 .addTag(addressForTag(mix)) 374 .build(), 375 mix.getFormat(), 376 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(), 377 mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()), 378 AudioTrack.MODE_STREAM, 379 AudioManager.AUDIO_SESSION_ID_GENERATE 380 ); 381 return at; 382 } 383 384 @SystemApi 385 public int getStatus() { 386 return mStatus; 387 } 388 389 @SystemApi 390 public static abstract class AudioPolicyStatusListener { 391 public void onStatusChange() {} 392 public void onMixStateUpdate(AudioMix mix) {} 393 } 394 395 @SystemApi 396 public static abstract class AudioPolicyFocusListener { 397 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {} 398 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {} 399 } 400 401 private void onPolicyStatusChange() { 402 AudioPolicyStatusListener l; 403 synchronized (mLock) { 404 if (mStatusListener == null) { 405 return; 406 } 407 l = mStatusListener; 408 } 409 l.onStatusChange(); 410 } 411 412 //================================================== 413 // Callback interface 414 415 /** @hide */ 416 public IAudioPolicyCallback cb() { return mPolicyCb; } 417 418 private final IAudioPolicyCallback mPolicyCb = new IAudioPolicyCallback.Stub() { 419 420 public void notifyAudioFocusGrant(AudioFocusInfo afi, int requestResult) { 421 sendMsg(MSG_FOCUS_GRANT, afi, requestResult); 422 if (DEBUG) { 423 Log.v(TAG, "notifyAudioFocusGrant: pack=" + afi.getPackageName() + " client=" 424 + afi.getClientId() + "reqRes=" + requestResult); 425 } 426 } 427 428 public void notifyAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { 429 sendMsg(MSG_FOCUS_LOSS, afi, wasNotified ? 1 : 0); 430 if (DEBUG) { 431 Log.v(TAG, "notifyAudioFocusLoss: pack=" + afi.getPackageName() + " client=" 432 + afi.getClientId() + "wasNotified=" + wasNotified); 433 } 434 } 435 }; 436 437 //================================================== 438 // Event handling 439 private final EventHandler mEventHandler; 440 private final static int MSG_POLICY_STATUS_CHANGE = 0; 441 private final static int MSG_FOCUS_GRANT = 1; 442 private final static int MSG_FOCUS_LOSS = 2; 443 444 private class EventHandler extends Handler { 445 public EventHandler(AudioPolicy ap, Looper looper) { 446 super(looper); 447 } 448 449 @Override 450 public void handleMessage(Message msg) { 451 switch(msg.what) { 452 case MSG_POLICY_STATUS_CHANGE: 453 onPolicyStatusChange(); 454 break; 455 case MSG_FOCUS_GRANT: 456 if (mFocusListener != null) { 457 mFocusListener.onAudioFocusGrant( 458 (AudioFocusInfo) msg.obj, msg.arg1); 459 } 460 break; 461 case MSG_FOCUS_LOSS: 462 if (mFocusListener != null) { 463 mFocusListener.onAudioFocusLoss( 464 (AudioFocusInfo) msg.obj, msg.arg1 != 0); 465 } 466 break; 467 default: 468 Log.e(TAG, "Unknown event " + msg.what); 469 } 470 } 471 } 472 473 //========================================================== 474 // Utils 475 private static String addressForTag(AudioMix mix) { 476 return "addr=" + mix.getRegistration(); 477 } 478 479 private void sendMsg(int msg) { 480 if (mEventHandler != null) { 481 mEventHandler.sendEmptyMessage(msg); 482 } 483 } 484 485 private void sendMsg(int msg, Object obj, int i) { 486 if (mEventHandler != null) { 487 mEventHandler.sendMessage( 488 mEventHandler.obtainMessage(msg, i /*arg1*/, 0 /*arg2, ignored*/, obj)); 489 } 490 } 491 492 private static IAudioService sService; 493 494 private static IAudioService getService() 495 { 496 if (sService != null) { 497 return sService; 498 } 499 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 500 sService = IAudioService.Stub.asInterface(b); 501 return sService; 502 } 503 504 public String toLogFriendlyString() { 505 String textDump = new String("android.media.audiopolicy.AudioPolicy:\n"); 506 textDump += "config=" + mConfig.toLogFriendlyString(); 507 return (textDump); 508 } 509 510 /** @hide */ 511 @IntDef({ 512 POLICY_STATUS_REGISTERED, 513 POLICY_STATUS_UNREGISTERED 514 }) 515 @Retention(RetentionPolicy.SOURCE) 516 public @interface PolicyStatus {} 517} 518