| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.media.audiopolicy; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.SystemApi; |
| import android.annotation.UnsupportedAppUsage; |
| import android.media.AudioDeviceInfo; |
| import android.media.AudioFormat; |
| import android.media.AudioSystem; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.Objects; |
| |
| /** |
| * @hide |
| */ |
| @SystemApi |
| public class AudioMix { |
| |
| @UnsupportedAppUsage |
| private AudioMixingRule mRule; |
| @UnsupportedAppUsage |
| private AudioFormat mFormat; |
| @UnsupportedAppUsage |
| private int mRouteFlags; |
| @UnsupportedAppUsage |
| private int mMixType = MIX_TYPE_INVALID; |
| |
| // written by AudioPolicy |
| int mMixState = MIX_STATE_DISABLED; |
| @UnsupportedAppUsage |
| int mCallbackFlags; |
| @UnsupportedAppUsage |
| String mDeviceAddress; |
| |
| // initialized in constructor, read by AudioPolicyConfig |
| @UnsupportedAppUsage |
| final int mDeviceSystemType; // an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_* |
| |
| /** |
| * All parameters are guaranteed valid through the Builder. |
| */ |
| private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags, |
| int deviceType, String deviceAddress) { |
| mRule = rule; |
| mFormat = format; |
| mRouteFlags = routeFlags; |
| mMixType = rule.getTargetMixType(); |
| mCallbackFlags = callbackFlags; |
| mDeviceSystemType = deviceType; |
| mDeviceAddress = (deviceAddress == null) ? new String("") : deviceAddress; |
| } |
| |
| // CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined |
| // in frameworks/av/include/media/AudioPolicy.h |
| /** @hide */ |
| public final static int CALLBACK_FLAG_NOTIFY_ACTIVITY = 0x1; |
| // when adding new MIX_FLAG_* flags, add them to this mask of authorized masks: |
| private final static int CALLBACK_FLAGS_ALL = CALLBACK_FLAG_NOTIFY_ACTIVITY; |
| |
| // ROUTE_FLAG_* values: keep in sync with MIX_ROUTE_FLAG_* values defined |
| // in frameworks/av/include/media/AudioPolicy.h |
| /** |
| * An audio mix behavior where the output of the mix is sent to the original destination of |
| * the audio signal, i.e. an output device for an output mix, or a recording for an input mix. |
| */ |
| public static final int ROUTE_FLAG_RENDER = 0x1; |
| /** |
| * An audio mix behavior where the output of the mix is rerouted back to the framework and |
| * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord} |
| * APIs. |
| */ |
| public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1; |
| |
| private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK; |
| |
| // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h |
| /** |
| * @hide |
| * Invalid mix type, default value. |
| */ |
| public static final int MIX_TYPE_INVALID = -1; |
| /** |
| * @hide |
| * Mix type indicating playback streams are mixed. |
| */ |
| public static final int MIX_TYPE_PLAYERS = 0; |
| /** |
| * @hide |
| * Mix type indicating recording streams are mixed. |
| */ |
| public static final int MIX_TYPE_RECORDERS = 1; |
| |
| |
| // MIX_STATE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h |
| /** |
| * State of a mix before its policy is enabled. |
| */ |
| public static final int MIX_STATE_DISABLED = -1; |
| /** |
| * State of a mix when there is no audio to mix. |
| */ |
| public static final int MIX_STATE_IDLE = 0; |
| /** |
| * State of a mix that is actively mixing audio. |
| */ |
| public static final int MIX_STATE_MIXING = 1; |
| |
| /** |
| * The current mixing state. |
| * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE}, |
| * {@link #MIX_STATE_MIXING}. |
| */ |
| public int getMixState() { |
| return mMixState; |
| } |
| |
| |
| /** @hide */ |
| public int getRouteFlags() { |
| return mRouteFlags; |
| } |
| |
| AudioFormat getFormat() { |
| return mFormat; |
| } |
| |
| AudioMixingRule getRule() { |
| return mRule; |
| } |
| |
| /** @hide */ |
| public int getMixType() { |
| return mMixType; |
| } |
| |
| void setRegistration(String regId) { |
| mDeviceAddress = regId; |
| } |
| |
| /** @hide */ |
| public String getRegistration() { |
| return mDeviceAddress; |
| } |
| |
| /** @hide */ |
| public boolean isAffectingUsage(int usage) { |
| return mRule.isAffectingUsage(usage); |
| } |
| |
| /** @hide */ |
| public boolean isRoutedToDevice(int deviceType, @NonNull String deviceAddress) { |
| if ((mRouteFlags & ROUTE_FLAG_RENDER) != ROUTE_FLAG_RENDER) { |
| return false; |
| } |
| if (deviceType != mDeviceSystemType) { |
| return false; |
| } |
| if (!deviceAddress.equals(mDeviceAddress)) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** @hide */ |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| final AudioMix that = (AudioMix) o; |
| return (this.mRouteFlags == that.mRouteFlags) |
| && (this.mRule == that.mRule) |
| && (this.mMixType == that.mMixType) |
| && (this.mFormat == that.mFormat); |
| } |
| |
| /** @hide */ |
| @Override |
| public int hashCode() { |
| return Objects.hash(mRouteFlags, mRule, mMixType, mFormat); |
| } |
| |
| /** @hide */ |
| @IntDef(flag = true, |
| value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } ) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface RouteFlags {} |
| |
| /** |
| * Builder class for {@link AudioMix} objects |
| */ |
| public static class Builder { |
| private AudioMixingRule mRule = null; |
| private AudioFormat mFormat = null; |
| private int mRouteFlags = 0; |
| private int mCallbackFlags = 0; |
| // an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_* |
| private int mDeviceSystemType = AudioSystem.DEVICE_NONE; |
| private String mDeviceAddress = null; |
| |
| /** |
| * @hide |
| * Only used by AudioPolicyConfig, not a public API. |
| */ |
| Builder() { } |
| |
| /** |
| * Construct an instance for the given {@link AudioMixingRule}. |
| * @param rule a non-null {@link AudioMixingRule} instance. |
| * @throws IllegalArgumentException |
| */ |
| public Builder(AudioMixingRule rule) |
| throws IllegalArgumentException { |
| if (rule == null) { |
| throw new IllegalArgumentException("Illegal null AudioMixingRule argument"); |
| } |
| mRule = rule; |
| } |
| |
| /** |
| * @hide |
| * Only used by AudioPolicyConfig, not a public API. |
| * @param rule |
| * @return the same Builder instance. |
| * @throws IllegalArgumentException |
| */ |
| Builder setMixingRule(AudioMixingRule rule) |
| throws IllegalArgumentException { |
| if (rule == null) { |
| throw new IllegalArgumentException("Illegal null AudioMixingRule argument"); |
| } |
| mRule = rule; |
| return this; |
| } |
| |
| /** |
| * @hide |
| * Only used by AudioPolicyConfig, not a public API. |
| * @param callbackFlags which callbacks are called from native |
| * @return the same Builder instance. |
| * @throws IllegalArgumentException |
| */ |
| Builder setCallbackFlags(int flags) throws IllegalArgumentException { |
| if ((flags != 0) && ((flags & CALLBACK_FLAGS_ALL) == 0)) { |
| throw new IllegalArgumentException("Illegal callback flags 0x" |
| + Integer.toHexString(flags).toUpperCase()); |
| } |
| mCallbackFlags = flags; |
| return this; |
| } |
| |
| /** |
| * @hide |
| * Only used by AudioPolicyConfig, not a public API. |
| * @param deviceType an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_* |
| * @param address |
| * @return the same Builder instance. |
| */ |
| Builder setDevice(int deviceType, String address) { |
| mDeviceSystemType = deviceType; |
| mDeviceAddress = address; |
| return this; |
| } |
| |
| /** |
| * Sets the {@link AudioFormat} for the mix. |
| * @param format a non-null {@link AudioFormat} instance. |
| * @return the same Builder instance. |
| * @throws IllegalArgumentException |
| */ |
| public Builder setFormat(AudioFormat format) |
| throws IllegalArgumentException { |
| if (format == null) { |
| throw new IllegalArgumentException("Illegal null AudioFormat argument"); |
| } |
| mFormat = format; |
| return this; |
| } |
| |
| /** |
| * Sets the routing behavior for the mix. If not set, routing behavior will default to |
| * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}. |
| * @param routeFlags one of {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, |
| * {@link AudioMix#ROUTE_FLAG_RENDER} |
| * @return the same Builder instance. |
| * @throws IllegalArgumentException |
| */ |
| public Builder setRouteFlags(@RouteFlags int routeFlags) |
| throws IllegalArgumentException { |
| if (routeFlags == 0) { |
| throw new IllegalArgumentException("Illegal empty route flags"); |
| } |
| if ((routeFlags & ROUTE_FLAG_SUPPORTED) == 0) { |
| throw new IllegalArgumentException("Invalid route flags 0x" |
| + Integer.toHexString(routeFlags) + "when configuring an AudioMix"); |
| } |
| if ((routeFlags & ~ROUTE_FLAG_SUPPORTED) != 0) { |
| throw new IllegalArgumentException("Unknown route flags 0x" |
| + Integer.toHexString(routeFlags) + "when configuring an AudioMix"); |
| } |
| mRouteFlags = routeFlags; |
| return this; |
| } |
| |
| /** |
| * Sets the audio device used for playback. Cannot be used in the context of an audio |
| * policy used to inject audio to be recorded, or in a mix whose route flags doesn't |
| * specify {@link AudioMix#ROUTE_FLAG_RENDER}. |
| * @param device a non-null AudioDeviceInfo describing the audio device to play the output |
| * of this mix. |
| * @return the same Builder instance |
| * @throws IllegalArgumentException |
| */ |
| public Builder setDevice(@NonNull AudioDeviceInfo device) throws IllegalArgumentException { |
| if (device == null) { |
| throw new IllegalArgumentException("Illegal null AudioDeviceInfo argument"); |
| } |
| if (!device.isSink()) { |
| throw new IllegalArgumentException("Unsupported device type on mix, not a sink"); |
| } |
| mDeviceSystemType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()); |
| mDeviceAddress = device.getAddress(); |
| return this; |
| } |
| |
| /** |
| * Combines all of the settings and return a new {@link AudioMix} object. |
| * @return a new {@link AudioMix} object |
| * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set. |
| */ |
| public AudioMix build() throws IllegalArgumentException { |
| if (mRule == null) { |
| throw new IllegalArgumentException("Illegal null AudioMixingRule"); |
| } |
| if (mRouteFlags == 0) { |
| // no route flags set, use default as described in Builder.setRouteFlags(int) |
| mRouteFlags = ROUTE_FLAG_LOOP_BACK; |
| } |
| if (mFormat == null) { |
| // FIXME Can we eliminate this? Will AudioMix work with an unspecified sample rate? |
| int rate = AudioSystem.getPrimaryOutputSamplingRate(); |
| if (rate <= 0) { |
| rate = 44100; |
| } |
| mFormat = new AudioFormat.Builder().setSampleRate(rate).build(); |
| } |
| if ((mDeviceSystemType != AudioSystem.DEVICE_NONE) |
| && (mDeviceSystemType != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) |
| && (mDeviceSystemType != AudioSystem.DEVICE_IN_REMOTE_SUBMIX)) { |
| if ((mRouteFlags & ROUTE_FLAG_RENDER) == 0) { |
| throw new IllegalArgumentException( |
| "Can't have audio device without flag ROUTE_FLAG_RENDER"); |
| } |
| if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) { |
| throw new IllegalArgumentException("Unsupported device on non-playback mix"); |
| } |
| } else { |
| if ((mRouteFlags & ROUTE_FLAG_SUPPORTED) == ROUTE_FLAG_RENDER) { |
| throw new IllegalArgumentException( |
| "Can't have flag ROUTE_FLAG_RENDER without an audio device"); |
| } |
| if ((mRouteFlags & ROUTE_FLAG_LOOP_BACK) == ROUTE_FLAG_LOOP_BACK) { |
| if (mRule.getTargetMixType() == MIX_TYPE_PLAYERS) { |
| mDeviceSystemType = AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; |
| } else if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) { |
| mDeviceSystemType = AudioSystem.DEVICE_IN_REMOTE_SUBMIX; |
| } else { |
| throw new IllegalArgumentException("Unknown mixing rule type"); |
| } |
| } |
| } |
| return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType, |
| mDeviceAddress); |
| } |
| } |
| } |