• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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