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
17package android.media.audiopolicy;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.SystemApi;
22import android.media.AudioDeviceInfo;
23import android.media.AudioFormat;
24import android.media.AudioSystem;
25
26import java.lang.annotation.Retention;
27import java.lang.annotation.RetentionPolicy;
28import java.util.Objects;
29
30/**
31 * @hide
32 */
33@SystemApi
34public class AudioMix {
35
36    private AudioMixingRule mRule;
37    private AudioFormat mFormat;
38    private int mRouteFlags;
39    private int mMixType = MIX_TYPE_INVALID;
40
41    // written by AudioPolicy
42    int mMixState = MIX_STATE_DISABLED;
43    int mCallbackFlags;
44    String mDeviceAddress;
45
46    // initialized in constructor, read by AudioPolicyConfig
47    final int mDeviceSystemType; // an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_*
48
49    /**
50     * All parameters are guaranteed valid through the Builder.
51     */
52    private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags,
53            int deviceType, String deviceAddress) {
54        mRule = rule;
55        mFormat = format;
56        mRouteFlags = routeFlags;
57        mMixType = rule.getTargetMixType();
58        mCallbackFlags = callbackFlags;
59        mDeviceSystemType = deviceType;
60        mDeviceAddress = (deviceAddress == null) ? new String("") : deviceAddress;
61    }
62
63    // CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined
64    // in frameworks/av/include/media/AudioPolicy.h
65    /** @hide */
66    public final static int CALLBACK_FLAG_NOTIFY_ACTIVITY = 0x1;
67    // when adding new MIX_FLAG_* flags, add them to this mask of authorized masks:
68    private final static int CALLBACK_FLAGS_ALL = CALLBACK_FLAG_NOTIFY_ACTIVITY;
69
70    // ROUTE_FLAG_* values: keep in sync with MIX_ROUTE_FLAG_* values defined
71    // in frameworks/av/include/media/AudioPolicy.h
72    /**
73     * An audio mix behavior where the output of the mix is sent to the original destination of
74     * the audio signal, i.e. an output device for an output mix, or a recording for an input mix.
75     */
76    @SystemApi
77    public static final int ROUTE_FLAG_RENDER    = 0x1;
78    /**
79     * An audio mix behavior where the output of the mix is rerouted back to the framework and
80     * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord}
81     * APIs.
82     */
83    @SystemApi
84    public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
85
86    private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK;
87
88    // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
89    /**
90     * @hide
91     * Invalid mix type, default value.
92     */
93    public static final int MIX_TYPE_INVALID = -1;
94    /**
95     * @hide
96     * Mix type indicating playback streams are mixed.
97     */
98    public static final int MIX_TYPE_PLAYERS = 0;
99    /**
100     * @hide
101     * Mix type indicating recording streams are mixed.
102     */
103    public static final int MIX_TYPE_RECORDERS = 1;
104
105
106    // MIX_STATE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
107    /**
108     * @hide
109     * State of a mix before its policy is enabled.
110     */
111    @SystemApi
112    public static final int MIX_STATE_DISABLED = -1;
113    /**
114     * @hide
115     * State of a mix when there is no audio to mix.
116     */
117    @SystemApi
118    public static final int MIX_STATE_IDLE = 0;
119    /**
120     * @hide
121     * State of a mix that is actively mixing audio.
122     */
123    @SystemApi
124    public static final int MIX_STATE_MIXING = 1;
125
126    /**
127     * @hide
128     * The current mixing state.
129     * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE},
130     *          {@link #MIX_STATE_MIXING}.
131     */
132    @SystemApi
133    public int getMixState() {
134        return mMixState;
135    }
136
137
138    int getRouteFlags() {
139        return mRouteFlags;
140    }
141
142    AudioFormat getFormat() {
143        return mFormat;
144    }
145
146    AudioMixingRule getRule() {
147        return mRule;
148    }
149
150    /** @hide */
151    public int getMixType() {
152        return mMixType;
153    }
154
155    void setRegistration(String regId) {
156        mDeviceAddress = regId;
157    }
158
159    /** @hide */
160    public String getRegistration() {
161        return mDeviceAddress;
162    }
163
164    /** @hide */
165    @Override
166    public int hashCode() {
167        return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
168    }
169
170    /** @hide */
171    @IntDef(flag = true,
172            value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
173    @Retention(RetentionPolicy.SOURCE)
174    public @interface RouteFlags {}
175
176    /**
177     * Builder class for {@link AudioMix} objects
178     *
179     */
180    @SystemApi
181    public static class Builder {
182        private AudioMixingRule mRule = null;
183        private AudioFormat mFormat = null;
184        private int mRouteFlags = 0;
185        private int mCallbackFlags = 0;
186        // an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_*
187        private int mDeviceSystemType = AudioSystem.DEVICE_NONE;
188        private String mDeviceAddress = null;
189
190        /**
191         * @hide
192         * Only used by AudioPolicyConfig, not a public API.
193         */
194        Builder() { }
195
196        /**
197         * Construct an instance for the given {@link AudioMixingRule}.
198         * @param rule a non-null {@link AudioMixingRule} instance.
199         * @throws IllegalArgumentException
200         */
201        @SystemApi
202        public Builder(AudioMixingRule rule)
203                throws IllegalArgumentException {
204            if (rule == null) {
205                throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
206            }
207            mRule = rule;
208        }
209
210        /**
211         * @hide
212         * Only used by AudioPolicyConfig, not a public API.
213         * @param rule
214         * @return the same Builder instance.
215         * @throws IllegalArgumentException
216         */
217        Builder setMixingRule(AudioMixingRule rule)
218                throws IllegalArgumentException {
219            if (rule == null) {
220                throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
221            }
222            mRule = rule;
223            return this;
224        }
225
226        /**
227         * @hide
228         * Only used by AudioPolicyConfig, not a public API.
229         * @param callbackFlags which callbacks are called from native
230         * @return the same Builder instance.
231         * @throws IllegalArgumentException
232         */
233        Builder setCallbackFlags(int flags) throws IllegalArgumentException {
234            if ((flags != 0) && ((flags & CALLBACK_FLAGS_ALL) == 0)) {
235                throw new IllegalArgumentException("Illegal callback flags 0x"
236                        + Integer.toHexString(flags).toUpperCase());
237            }
238            mCallbackFlags = flags;
239            return this;
240        }
241
242        /**
243         * @hide
244         * Only used by AudioPolicyConfig, not a public API.
245         * @param deviceType an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_*
246         * @param address
247         * @return the same Builder instance.
248         */
249        Builder setDevice(int deviceType, String address) {
250            mDeviceSystemType = deviceType;
251            mDeviceAddress = address;
252            return this;
253        }
254
255        /**
256         * Sets the {@link AudioFormat} for the mix.
257         * @param format a non-null {@link AudioFormat} instance.
258         * @return the same Builder instance.
259         * @throws IllegalArgumentException
260         */
261        @SystemApi
262        public Builder setFormat(AudioFormat format)
263                throws IllegalArgumentException {
264            if (format == null) {
265                throw new IllegalArgumentException("Illegal null AudioFormat argument");
266            }
267            mFormat = format;
268            return this;
269        }
270
271        /**
272         * Sets the routing behavior for the mix. If not set, routing behavior will default to
273         * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}.
274         * @param routeFlags one of {@link AudioMix#ROUTE_FLAG_LOOP_BACK},
275         *     {@link AudioMix#ROUTE_FLAG_RENDER}
276         * @return the same Builder instance.
277         * @throws IllegalArgumentException
278         */
279        @SystemApi
280        public Builder setRouteFlags(@RouteFlags int routeFlags)
281                throws IllegalArgumentException {
282            if (routeFlags == 0) {
283                throw new IllegalArgumentException("Illegal empty route flags");
284            }
285            if ((routeFlags & ROUTE_FLAG_SUPPORTED) == 0) {
286                throw new IllegalArgumentException("Invalid route flags 0x"
287                        + Integer.toHexString(routeFlags) + "when configuring an AudioMix");
288            }
289            if ((routeFlags & ~ROUTE_FLAG_SUPPORTED) != 0) {
290                throw new IllegalArgumentException("Unknown route flags 0x"
291                        + Integer.toHexString(routeFlags) + "when configuring an AudioMix");
292            }
293            mRouteFlags = routeFlags;
294            return this;
295        }
296
297        /**
298         * Sets the audio device used for playback. Cannot be used in the context of an audio
299         * policy used to inject audio to be recorded, or in a mix whose route flags doesn't
300         * specify {@link AudioMix#ROUTE_FLAG_RENDER}.
301         * @param device a non-null AudioDeviceInfo describing the audio device to play the output
302         *     of this mix.
303         * @return the same Builder instance
304         * @throws IllegalArgumentException
305         */
306        @SystemApi
307        public Builder setDevice(@NonNull AudioDeviceInfo device) throws IllegalArgumentException {
308            if (device == null) {
309                throw new IllegalArgumentException("Illegal null AudioDeviceInfo argument");
310            }
311            if (!device.isSink()) {
312                throw new IllegalArgumentException("Unsupported device type on mix, not a sink");
313            }
314            mDeviceSystemType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType());
315            mDeviceAddress = device.getAddress();
316            return this;
317        }
318
319        /**
320         * Combines all of the settings and return a new {@link AudioMix} object.
321         * @return a new {@link AudioMix} object
322         * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
323         */
324        @SystemApi
325        public AudioMix build() throws IllegalArgumentException {
326            if (mRule == null) {
327                throw new IllegalArgumentException("Illegal null AudioMixingRule");
328            }
329            if (mRouteFlags == 0) {
330                // no route flags set, use default as described in Builder.setRouteFlags(int)
331                mRouteFlags = ROUTE_FLAG_LOOP_BACK;
332            }
333            // can't do loop back AND render at same time in this implementation
334            if (mRouteFlags == (ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK)) {
335                throw new IllegalArgumentException("Unsupported route behavior combination 0x" +
336                        Integer.toHexString(mRouteFlags));
337            }
338            if (mFormat == null) {
339                // FIXME Can we eliminate this?  Will AudioMix work with an unspecified sample rate?
340                int rate = AudioSystem.getPrimaryOutputSamplingRate();
341                if (rate <= 0) {
342                    rate = 44100;
343                }
344                mFormat = new AudioFormat.Builder().setSampleRate(rate).build();
345            }
346            if ((mDeviceSystemType != AudioSystem.DEVICE_NONE)
347                    && (mDeviceSystemType != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)
348                    && (mDeviceSystemType != AudioSystem.DEVICE_IN_REMOTE_SUBMIX)) {
349                if ((mRouteFlags & ROUTE_FLAG_RENDER) == 0) {
350                    throw new IllegalArgumentException(
351                            "Can't have audio device without flag ROUTE_FLAG_RENDER");
352                }
353                if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) {
354                    throw new IllegalArgumentException("Unsupported device on non-playback mix");
355                }
356            } else {
357                if ((mRouteFlags & ROUTE_FLAG_RENDER) == ROUTE_FLAG_RENDER) {
358                    throw new IllegalArgumentException(
359                            "Can't have flag ROUTE_FLAG_RENDER without an audio device");
360                }
361                if ((mRouteFlags & ROUTE_FLAG_SUPPORTED) == ROUTE_FLAG_LOOP_BACK) {
362                    if (mRule.getTargetMixType() == MIX_TYPE_PLAYERS) {
363                        mDeviceSystemType = AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
364                    } else if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) {
365                        mDeviceSystemType = AudioSystem.DEVICE_IN_REMOTE_SUBMIX;
366                    } else {
367                        throw new IllegalArgumentException("Unknown mixing rule type");
368                    }
369                }
370            }
371            return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType,
372                    mDeviceAddress);
373        }
374    }
375}
376