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