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 17 package android.media.audiopolicy; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.SystemApi; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.media.AudioDeviceInfo; 24 import android.media.AudioFormat; 25 import android.media.AudioSystem; 26 import android.os.Build; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.util.Objects; 33 34 /** 35 * @hide 36 */ 37 @SystemApi 38 public class AudioMix { 39 40 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 41 private AudioMixingRule mRule; 42 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 43 private AudioFormat mFormat; 44 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 45 private int mRouteFlags; 46 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 47 private int mMixType = MIX_TYPE_INVALID; 48 49 // written by AudioPolicy 50 int mMixState = MIX_STATE_DISABLED; 51 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 52 int mCallbackFlags; 53 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 54 String mDeviceAddress; 55 56 // initialized in constructor, read by AudioPolicyConfig 57 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 58 final int mDeviceSystemType; // an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_* 59 60 /** 61 * All parameters are guaranteed valid through the Builder. 62 */ AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags, int deviceType, String deviceAddress)63 private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags, 64 int deviceType, String deviceAddress) { 65 mRule = rule; 66 mFormat = format; 67 mRouteFlags = routeFlags; 68 mMixType = rule.getTargetMixType(); 69 mCallbackFlags = callbackFlags; 70 mDeviceSystemType = deviceType; 71 mDeviceAddress = (deviceAddress == null) ? new String("") : deviceAddress; 72 } 73 74 // CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined 75 // in frameworks/av/include/media/AudioPolicy.h 76 /** @hide */ 77 public final static int CALLBACK_FLAG_NOTIFY_ACTIVITY = 0x1; 78 // when adding new MIX_FLAG_* flags, add them to this mask of authorized masks: 79 private final static int CALLBACK_FLAGS_ALL = CALLBACK_FLAG_NOTIFY_ACTIVITY; 80 81 // ROUTE_FLAG_* values: keep in sync with MIX_ROUTE_FLAG_* values defined 82 // in frameworks/av/include/media/AudioPolicy.h 83 /** 84 * An audio mix behavior where the output of the mix is sent to the original destination of 85 * the audio signal, i.e. an output device for an output mix, or a recording for an input mix. 86 */ 87 public static final int ROUTE_FLAG_RENDER = 0x1; 88 /** 89 * An audio mix behavior where the output of the mix is rerouted back to the framework and 90 * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord} 91 * APIs. 92 */ 93 public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1; 94 95 /** 96 * An audio mix behavior where the targeted audio is played unaffected but a copy is 97 * accessible for capture through {@link AudioRecord}. 98 * 99 * Only capture of playback is supported, not capture of capture. 100 * Use concurrent capture instead to capture what is captured by other apps. 101 * 102 * The captured audio is an approximation of the played audio. 103 * Effects and volume are not applied, and track are mixed with different delay then in the HAL. 104 * As a result, this API is not suitable for echo cancelling. 105 * @hide 106 */ 107 public static final int ROUTE_FLAG_LOOP_BACK_RENDER = ROUTE_FLAG_LOOP_BACK | ROUTE_FLAG_RENDER; 108 109 private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK; 110 111 // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h 112 /** 113 * @hide 114 * Invalid mix type, default value. 115 */ 116 public static final int MIX_TYPE_INVALID = -1; 117 /** 118 * @hide 119 * Mix type indicating playback streams are mixed. 120 */ 121 public static final int MIX_TYPE_PLAYERS = 0; 122 /** 123 * @hide 124 * Mix type indicating recording streams are mixed. 125 */ 126 public static final int MIX_TYPE_RECORDERS = 1; 127 128 129 // MIX_STATE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h 130 /** 131 * State of a mix before its policy is enabled. 132 */ 133 public static final int MIX_STATE_DISABLED = -1; 134 /** 135 * State of a mix when there is no audio to mix. 136 */ 137 public static final int MIX_STATE_IDLE = 0; 138 /** 139 * State of a mix that is actively mixing audio. 140 */ 141 public static final int MIX_STATE_MIXING = 1; 142 143 /** Maximum sampling rate for privileged playback capture*/ 144 private static final int PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE = 16000; 145 146 /** Maximum channel number for privileged playback capture*/ 147 private static final int PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER = 1; 148 149 /** Maximum channel number for privileged playback capture*/ 150 private static final int PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE = 2; 151 152 /** 153 * The current mixing state. 154 * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE}, 155 * {@link #MIX_STATE_MIXING}. 156 */ getMixState()157 public int getMixState() { 158 return mMixState; 159 } 160 161 162 /** @hide */ getRouteFlags()163 public int getRouteFlags() { 164 return mRouteFlags; 165 } 166 167 /** @hide */ getFormat()168 public AudioFormat getFormat() { 169 return mFormat; 170 } 171 172 /** @hide */ getRule()173 public AudioMixingRule getRule() { 174 return mRule; 175 } 176 177 /** @hide */ getMixType()178 public int getMixType() { 179 return mMixType; 180 } 181 setRegistration(String regId)182 void setRegistration(String regId) { 183 mDeviceAddress = regId; 184 } 185 186 /** @hide */ getRegistration()187 public String getRegistration() { 188 return mDeviceAddress; 189 } 190 191 /** @hide */ isAffectingUsage(int usage)192 public boolean isAffectingUsage(int usage) { 193 return mRule.isAffectingUsage(usage); 194 } 195 196 /** 197 * Returns {@code true} if the rule associated with this mix contains a 198 * RULE_MATCH_ATTRIBUTE_USAGE criterion for the given usage 199 * 200 * @hide 201 */ containsMatchAttributeRuleForUsage(int usage)202 public boolean containsMatchAttributeRuleForUsage(int usage) { 203 return mRule.containsMatchAttributeRuleForUsage(usage); 204 } 205 206 /** @hide */ isRoutedToDevice(int deviceType, @NonNull String deviceAddress)207 public boolean isRoutedToDevice(int deviceType, @NonNull String deviceAddress) { 208 if ((mRouteFlags & ROUTE_FLAG_RENDER) != ROUTE_FLAG_RENDER) { 209 return false; 210 } 211 if (deviceType != mDeviceSystemType) { 212 return false; 213 } 214 if (!deviceAddress.equals(mDeviceAddress)) { 215 return false; 216 } 217 return true; 218 } 219 220 /** @return an error string if the format would not allow Privileged playbackCapture 221 * null otherwise 222 * @hide */ canBeUsedForPrivilegedMediaCapture(AudioFormat format)223 public static String canBeUsedForPrivilegedMediaCapture(AudioFormat format) { 224 int sampleRate = format.getSampleRate(); 225 if (sampleRate > PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE || sampleRate <= 0) { 226 return "Privileged audio capture sample rate " + sampleRate 227 + " can not be over " + PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE + "kHz"; 228 } 229 int channelCount = format.getChannelCount(); 230 if (channelCount > PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER || channelCount <= 0) { 231 return "Privileged audio capture channel count " + channelCount + " can not be over " 232 + PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER; 233 } 234 int encoding = format.getEncoding(); 235 if (!format.isPublicEncoding(encoding) || !format.isEncodingLinearPcm(encoding)) { 236 return "Privileged audio capture encoding " + encoding + "is not linear"; 237 } 238 if (format.getBytesPerSample(encoding) > PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE) { 239 return "Privileged audio capture encoding " + encoding + " can not be over " 240 + PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE + " bytes per sample"; 241 } 242 return null; 243 } 244 245 /** @hide */ isForCallRedirection()246 public boolean isForCallRedirection() { 247 return mRule.isForCallRedirection(); 248 } 249 250 /** @hide */ 251 @Override equals(Object o)252 public boolean equals(Object o) { 253 if (this == o) return true; 254 if (o == null || getClass() != o.getClass()) return false; 255 256 final AudioMix that = (AudioMix) o; 257 return Objects.equals(this.mRouteFlags, that.mRouteFlags) 258 && Objects.equals(this.mRule, that.mRule) 259 && Objects.equals(this.mMixType, that.mMixType) 260 && Objects.equals(this.mFormat, that.mFormat); 261 } 262 263 /** @hide */ 264 @Override hashCode()265 public int hashCode() { 266 return Objects.hash(mRouteFlags, mRule, mMixType, mFormat); 267 } 268 269 /** @hide */ 270 @IntDef(flag = true, 271 value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } ) 272 @Retention(RetentionPolicy.SOURCE) 273 public @interface RouteFlags {} 274 275 /** 276 * Builder class for {@link AudioMix} objects 277 */ 278 public static class Builder { 279 private AudioMixingRule mRule = null; 280 private AudioFormat mFormat = null; 281 private int mRouteFlags = 0; 282 private int mCallbackFlags = 0; 283 // an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_* 284 private int mDeviceSystemType = AudioSystem.DEVICE_NONE; 285 private String mDeviceAddress = null; 286 287 /** 288 * @hide 289 * Only used by AudioPolicyConfig, not a public API. 290 */ Builder()291 Builder() { } 292 293 /** 294 * Construct an instance for the given {@link AudioMixingRule}. 295 * @param rule a non-null {@link AudioMixingRule} instance. 296 * @throws IllegalArgumentException 297 */ Builder(AudioMixingRule rule)298 public Builder(AudioMixingRule rule) 299 throws IllegalArgumentException { 300 if (rule == null) { 301 throw new IllegalArgumentException("Illegal null AudioMixingRule argument"); 302 } 303 mRule = rule; 304 } 305 306 /** 307 * @hide 308 * Only used by AudioPolicyConfig, not a public API. 309 * @param rule 310 * @return the same Builder instance. 311 * @throws IllegalArgumentException 312 */ setMixingRule(AudioMixingRule rule)313 Builder setMixingRule(AudioMixingRule rule) 314 throws IllegalArgumentException { 315 if (rule == null) { 316 throw new IllegalArgumentException("Illegal null AudioMixingRule argument"); 317 } 318 mRule = rule; 319 return this; 320 } 321 322 /** 323 * @hide 324 * Only used by AudioPolicyConfig, not a public API. 325 * @param callbackFlags which callbacks are called from native 326 * @return the same Builder instance. 327 * @throws IllegalArgumentException 328 */ setCallbackFlags(int flags)329 Builder setCallbackFlags(int flags) throws IllegalArgumentException { 330 if ((flags != 0) && ((flags & CALLBACK_FLAGS_ALL) == 0)) { 331 throw new IllegalArgumentException("Illegal callback flags 0x" 332 + Integer.toHexString(flags).toUpperCase()); 333 } 334 mCallbackFlags = flags; 335 return this; 336 } 337 338 /** 339 * @hide 340 * Only used by AudioPolicyConfig, not a public API. 341 * @param deviceType an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_* 342 * @param address 343 * @return the same Builder instance. 344 */ 345 @VisibleForTesting setDevice(int deviceType, String address)346 public Builder setDevice(int deviceType, String address) { 347 mDeviceSystemType = deviceType; 348 mDeviceAddress = address; 349 return this; 350 } 351 352 /** 353 * Sets the {@link AudioFormat} for the mix. 354 * @param format a non-null {@link AudioFormat} instance. 355 * @return the same Builder instance. 356 * @throws IllegalArgumentException 357 */ setFormat(AudioFormat format)358 public Builder setFormat(AudioFormat format) 359 throws IllegalArgumentException { 360 if (format == null) { 361 throw new IllegalArgumentException("Illegal null AudioFormat argument"); 362 } 363 mFormat = format; 364 return this; 365 } 366 367 /** 368 * Sets the routing behavior for the mix. If not set, routing behavior will default to 369 * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}. 370 * @param routeFlags one of {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, 371 * {@link AudioMix#ROUTE_FLAG_RENDER} 372 * @return the same Builder instance. 373 * @throws IllegalArgumentException 374 */ setRouteFlags(@outeFlags int routeFlags)375 public Builder setRouteFlags(@RouteFlags int routeFlags) 376 throws IllegalArgumentException { 377 if (routeFlags == 0) { 378 throw new IllegalArgumentException("Illegal empty route flags"); 379 } 380 if ((routeFlags & ROUTE_FLAG_SUPPORTED) == 0) { 381 throw new IllegalArgumentException("Invalid route flags 0x" 382 + Integer.toHexString(routeFlags) + "when configuring an AudioMix"); 383 } 384 if ((routeFlags & ~ROUTE_FLAG_SUPPORTED) != 0) { 385 throw new IllegalArgumentException("Unknown route flags 0x" 386 + Integer.toHexString(routeFlags) + "when configuring an AudioMix"); 387 } 388 mRouteFlags = routeFlags; 389 return this; 390 } 391 392 /** 393 * Sets the audio device used for playback. Cannot be used in the context of an audio 394 * policy used to inject audio to be recorded, or in a mix whose route flags doesn't 395 * specify {@link AudioMix#ROUTE_FLAG_RENDER}. 396 * @param device a non-null AudioDeviceInfo describing the audio device to play the output 397 * of this mix. 398 * @return the same Builder instance 399 * @throws IllegalArgumentException 400 */ setDevice(@onNull AudioDeviceInfo device)401 public Builder setDevice(@NonNull AudioDeviceInfo device) throws IllegalArgumentException { 402 if (device == null) { 403 throw new IllegalArgumentException("Illegal null AudioDeviceInfo argument"); 404 } 405 if (!device.isSink()) { 406 throw new IllegalArgumentException("Unsupported device type on mix, not a sink"); 407 } 408 mDeviceSystemType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()); 409 mDeviceAddress = device.getAddress(); 410 return this; 411 } 412 413 /** 414 * Combines all of the settings and return a new {@link AudioMix} object. 415 * @return a new {@link AudioMix} object 416 * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set. 417 */ build()418 public AudioMix build() throws IllegalArgumentException { 419 if (mRule == null) { 420 throw new IllegalArgumentException("Illegal null AudioMixingRule"); 421 } 422 if (mRouteFlags == 0) { 423 // no route flags set, use default as described in Builder.setRouteFlags(int) 424 mRouteFlags = ROUTE_FLAG_LOOP_BACK; 425 } 426 if (mFormat == null) { 427 // FIXME Can we eliminate this? Will AudioMix work with an unspecified sample rate? 428 int rate = AudioSystem.getPrimaryOutputSamplingRate(); 429 if (rate <= 0) { 430 rate = 44100; 431 } 432 mFormat = new AudioFormat.Builder().setSampleRate(rate).build(); 433 } else { 434 // Ensure that 'mFormat' uses an output channel mask. Using an input channel 435 // mask was not made 'illegal' initially, however the framework code 436 // assumes usage in AudioMixes of output channel masks only (b/194910301). 437 if ((mFormat.getPropertySetMask() 438 & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0) { 439 if (mFormat.getChannelCount() == 1 440 && mFormat.getChannelMask() == AudioFormat.CHANNEL_IN_MONO) { 441 mFormat = new AudioFormat.Builder(mFormat).setChannelMask( 442 AudioFormat.CHANNEL_OUT_MONO).build(); 443 } 444 // CHANNEL_IN_STEREO == CHANNEL_OUT_STEREO so no need to correct. 445 // CHANNEL_IN_FRONT_BACK is hidden, should not appear. 446 } 447 } 448 if ((mDeviceSystemType != AudioSystem.DEVICE_NONE) 449 && (mDeviceSystemType != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) 450 && (mDeviceSystemType != AudioSystem.DEVICE_IN_REMOTE_SUBMIX)) { 451 if ((mRouteFlags & ROUTE_FLAG_RENDER) == 0) { 452 throw new IllegalArgumentException( 453 "Can't have audio device without flag ROUTE_FLAG_RENDER"); 454 } 455 if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) { 456 throw new IllegalArgumentException("Unsupported device on non-playback mix"); 457 } 458 } else if (mDeviceSystemType == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) { 459 if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) { 460 throw new IllegalArgumentException( 461 "DEVICE_OUT_REMOTE_SUBMIX device is not supported on non-playback mix"); 462 } 463 } else { 464 if ((mRouteFlags & ROUTE_FLAG_SUPPORTED) == ROUTE_FLAG_RENDER) { 465 throw new IllegalArgumentException( 466 "Can't have flag ROUTE_FLAG_RENDER without an audio device"); 467 } 468 if ((mRouteFlags & ROUTE_FLAG_LOOP_BACK) == ROUTE_FLAG_LOOP_BACK) { 469 if (mRule.getTargetMixType() == MIX_TYPE_PLAYERS) { 470 mDeviceSystemType = AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; 471 } else if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) { 472 mDeviceSystemType = AudioSystem.DEVICE_IN_REMOTE_SUBMIX; 473 } else { 474 throw new IllegalArgumentException("Unknown mixing rule type"); 475 } 476 } 477 } 478 if (mRule.allowPrivilegedMediaPlaybackCapture()) { 479 String error = AudioMix.canBeUsedForPrivilegedMediaCapture(mFormat); 480 if (error != null) { 481 throw new IllegalArgumentException(error); 482 } 483 } 484 return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType, 485 mDeviceAddress); 486 } 487 } 488 } 489