AudioPolicy.java revision 8fdb0d4defb6ee2ca8057d3442ead36b408b6c17
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.content.Context;
21import android.content.pm.PackageManager;
22import android.media.AudioAttributes;
23import android.media.AudioFormat;
24import android.media.AudioManager;
25import android.media.AudioRecord;
26import android.media.AudioSystem;
27import android.media.AudioTrack;
28import android.media.MediaRecorder;
29import android.os.Binder;
30import android.os.IBinder;
31import android.util.Log;
32import android.util.Slog;
33
34import java.lang.annotation.Retention;
35import java.lang.annotation.RetentionPolicy;
36import java.util.ArrayList;
37
38/**
39 * @hide
40 * AudioPolicy provides access to the management of audio routing and audio focus.
41 */
42public class AudioPolicy {
43
44    private static final String TAG = "AudioPolicy";
45
46    /**
47     * The status of an audio policy that cannot be used because it is invalid.
48     */
49    public static final int POLICY_STATUS_INVALID = 0;
50    /**
51     * The status of an audio policy that is valid but cannot be used because it is not registered.
52     */
53    public static final int POLICY_STATUS_UNREGISTERED = 1;
54    /**
55     * The status of an audio policy that is valid, successfully registered and thus active.
56     */
57    public static final int POLICY_STATUS_REGISTERED = 2;
58
59    private int mStatus;
60    private String mRegistrationId;
61    private AudioPolicyStatusListener mStatusListener;
62
63    private final IBinder mToken = new Binder();
64    /** @hide */
65    public IBinder token() { return mToken; }
66    private Context mContext;
67
68    private AudioPolicyConfig mConfig;
69    /** @hide */
70    public AudioPolicyConfig getConfig() { return mConfig; }
71
72    /**
73     * The parameter is guaranteed non-null through the Builder
74     */
75    private AudioPolicy(AudioPolicyConfig config, Context context) {
76        mConfig = config;
77        if (mConfig.mMixes.isEmpty()) {
78            mStatus = POLICY_STATUS_INVALID;
79        } else {
80            mStatus = POLICY_STATUS_UNREGISTERED;
81        }
82        mContext = context;
83    }
84
85    /**
86     * Builder class for {@link AudioPolicy} objects
87     */
88    public static class Builder {
89        private ArrayList<AudioMix> mMixes;
90        private Context mContext;
91
92        /**
93         * Constructs a new Builder with no audio mixes.
94         * @param context the context for the policy
95         */
96        public Builder(Context context) {
97            mMixes = new ArrayList<AudioMix>();
98            mContext = context;
99        }
100
101        /**
102         * Add an {@link AudioMix} to be part of the audio policy being built.
103         * @param mix a non-null {@link AudioMix} to be part of the audio policy.
104         * @return the same Builder instance.
105         * @throws IllegalArgumentException
106         */
107        public Builder addMix(AudioMix mix) throws IllegalArgumentException {
108            if (mix == null) {
109                throw new IllegalArgumentException("Illegal null AudioMix argument");
110            }
111            mMixes.add(mix);
112            return this;
113        }
114
115        public AudioPolicy build() {
116            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext);
117        }
118    }
119
120    /** @hide */
121    public void setRegistration(String regId) {
122        mRegistrationId = regId;
123        mConfig.setRegistration(regId);
124    }
125
126    private boolean policyReadyToUse() {
127        if (mContext == null) {
128            Log.e(TAG, "Cannot use AudioPolicy without context");
129            return false;
130        }
131        if (mRegistrationId == null) {
132            Log.e(TAG, "Cannot use unregistered AudioPolicy");
133            return false;
134        }
135        if (!(PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
136                        android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
137            Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
138                    + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING");
139            return false;
140        }
141        return true;
142    }
143
144    private void checkMixReadyToUse(AudioMix mix, boolean forTrack)
145            throws IllegalArgumentException{
146        if (mix == null) {
147            String msg = forTrack ? "Invalid null AudioMix for AudioTrack creation"
148                    : "Invalid null AudioMix for AudioRecord creation";
149            throw new IllegalArgumentException(msg);
150        }
151        if (!mConfig.mMixes.contains(mix)) {
152            throw new IllegalArgumentException("Invalid mix: not part of this policy");
153        }
154        if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) != AudioMix.ROUTE_FLAG_LOOP_BACK)
155        {
156            throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
157        }
158    }
159
160    /**
161     * @hide
162     * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
163     * Audio buffers recorded through the created instance will contain the mix of the audio
164     * streams that fed the given mixer.
165     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
166     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
167     * @return a new {@link AudioRecord} instance whose data format is the one defined in the
168     *     {@link AudioMix}, or null if this policy was not successfully registered
169     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
170     * @throws IllegalArgumentException
171     */
172    public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
173        if (!policyReadyToUse()) {
174            Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
175            return null;
176        }
177        checkMixReadyToUse(mix, false/*not for an AudioTrack*/);
178        // create the AudioRecord, configured for loop back, using the same format as the mix
179        AudioRecord ar = new AudioRecord(
180                new AudioAttributes.Builder()
181                        .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
182                        .addTag(mix.getRegistration())
183                        .build(),
184                mix.getFormat(),
185                AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
186                        // using stereo for buffer size to avoid the current poor support for masks
187                        AudioFormat.CHANNEL_IN_STEREO, mix.getFormat().getEncoding()),
188                AudioManager.AUDIO_SESSION_ID_GENERATE
189                );
190        return ar;
191    }
192
193    /**
194     * @hide
195     * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
196     * Audio buffers played through the created instance will be sent to the given mix
197     * to be recorded through the recording APIs.
198     * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
199     *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
200     * @returna new {@link AudioTrack} instance whose data format is the one defined in the
201     *     {@link AudioMix}, or null if this policy was not successfully registered
202     *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
203     * @throws IllegalArgumentException
204     */
205    public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
206        if (!policyReadyToUse()) {
207            Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
208            return null;
209        }
210        checkMixReadyToUse(mix, true/*for an AudioTrack*/);
211        // create the AudioTrack, configured for loop back, using the same format as the mix
212        AudioTrack at = new AudioTrack(
213                new AudioAttributes.Builder()
214                        .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
215                        .addTag(mix.getRegistration())
216                        .build(),
217                mix.getFormat(),
218                AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
219                        mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
220                AudioTrack.MODE_STREAM,
221                AudioManager.AUDIO_SESSION_ID_GENERATE
222                );
223        return at;
224    }
225
226    public int getStatus() {
227        return mStatus;
228    }
229
230    public static abstract class AudioPolicyStatusListener {
231        void onStatusChange() {}
232        void onMixStateUpdate(AudioMix mix) {}
233    }
234
235    void setStatusListener(AudioPolicyStatusListener l) {
236        mStatusListener = l;
237    }
238
239    /** @hide */
240    public String toLogFriendlyString() {
241        String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
242        textDump += "config=" + mConfig.toLogFriendlyString();
243        return (textDump);
244    }
245
246    /** @hide */
247    @IntDef({
248        POLICY_STATUS_INVALID,
249        POLICY_STATUS_REGISTERED,
250        POLICY_STATUS_UNREGISTERED
251    })
252    @Retention(RetentionPolicy.SOURCE)
253    public @interface PolicyStatus {}
254}
255