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