1 /*
2  * Copyright (C) 2010 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.net.rtp;
18 
19 import android.annotation.NonNull;
20 import android.app.ActivityThread;
21 import android.content.Context;
22 import android.media.AudioManager;
23 
24 import java.util.HashMap;
25 import java.util.Locale;
26 import java.util.Map;
27 
28 /**
29  * An AudioGroup is an audio hub for the speaker, the microphone, and
30  * {@link AudioStream}s. Each of these components can be logically turned on
31  * or off by calling {@link #setMode(int)} or {@link RtpStream#setMode(int)}.
32  * The AudioGroup will go through these components and process them one by one
33  * within its execution loop. The loop consists of four steps. First, for each
34  * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming
35  * packets and stores in its buffer. Then, if the microphone is enabled,
36  * processes the recorded audio and stores in its buffer. Third, if the speaker
37  * is enabled, mixes all AudioStream buffers and plays back. Finally, for each
38  * AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other
39  * buffers and sends back the encoded packets. An AudioGroup does nothing if
40  * there is no AudioStream in it.
41  *
42  * <p>Few things must be noticed before using these classes. The performance is
43  * highly related to the system load and the network bandwidth. Usually a
44  * simpler {@link AudioCodec} costs fewer CPU cycles but requires more network
45  * bandwidth, and vise versa. Using two AudioStreams at the same time doubles
46  * not only the load but also the bandwidth. The condition varies from one
47  * device to another, and developers should choose the right combination in
48  * order to get the best result.</p>
49  *
50  * <p>It is sometimes useful to keep multiple AudioGroups at the same time. For
51  * example, a Voice over IP (VoIP) application might want to put a conference
52  * call on hold in order to make a new call but still allow people in the
53  * conference call talking to each other. This can be done easily using two
54  * AudioGroups, but there are some limitations. Since the speaker and the
55  * microphone are globally shared resources, only one AudioGroup at a time is
56  * allowed to run in a mode other than {@link #MODE_ON_HOLD}. The others will
57  * be unable to acquire these resources and fail silently.</p>
58  *
59  * <p class="note">Using this class requires
60  * {@link android.Manifest.permission#RECORD_AUDIO} permission. Developers
61  * should set the audio mode to {@link AudioManager#MODE_IN_COMMUNICATION}
62  * using {@link AudioManager#setMode(int)} and change it back when none of
63  * the AudioGroups is in use.</p>
64  *
65  * @see AudioStream
66  */
67 public class AudioGroup {
68     /**
69      * This mode is similar to {@link #MODE_NORMAL} except the speaker and
70      * the microphone are both disabled.
71      */
72     public static final int MODE_ON_HOLD = 0;
73 
74     /**
75      * This mode is similar to {@link #MODE_NORMAL} except the microphone is
76      * disabled.
77      */
78     public static final int MODE_MUTED = 1;
79 
80     /**
81      * This mode indicates that the speaker, the microphone, and all
82      * {@link AudioStream}s in the group are enabled. First, the packets
83      * received from the streams are decoded and mixed with the audio recorded
84      * from the microphone. Then, the results are played back to the speaker,
85      * encoded and sent back to each stream.
86      */
87     public static final int MODE_NORMAL = 2;
88 
89     /**
90      * This mode is similar to {@link #MODE_NORMAL} except the echo suppression
91      * is enabled. It should be only used when the speaker phone is on.
92      */
93     public static final int MODE_ECHO_SUPPRESSION = 3;
94 
95     private static final int MODE_LAST = 3;
96 
97     private final Map<AudioStream, Long> mStreams;
98     private int mMode = MODE_ON_HOLD;
99 
100     private long mNative;
101     private Context mContext;
102     static {
103         System.loadLibrary("rtp_jni");
104     }
105 
106     /**
107      * Creates an empty AudioGroup.
108      * @deprecated Replaced by {@link #AudioGroup(Context)}
109      */
110     @Deprecated
AudioGroup()111     public AudioGroup() {
112         this(null);
113     }
114 
115     /**
116      * Creates an empty AudioGroup.
117      * @param context Context used to get package name
118      */
AudioGroup(@onNull Context context)119     public AudioGroup(@NonNull Context context) {
120         mContext = context;
121         mStreams = new HashMap<AudioStream, Long>();
122     }
123 
124     /**
125      * Returns the {@link AudioStream}s in this group.
126      */
getStreams()127     public AudioStream[] getStreams() {
128         synchronized (this) {
129             return mStreams.keySet().toArray(new AudioStream[mStreams.size()]);
130         }
131     }
132 
133     /**
134      * Returns the current mode.
135      */
getMode()136     public int getMode() {
137         return mMode;
138     }
139 
140     /**
141      * Changes the current mode. It must be one of {@link #MODE_ON_HOLD},
142      * {@link #MODE_MUTED}, {@link #MODE_NORMAL}, and
143      * {@link #MODE_ECHO_SUPPRESSION}.
144      *
145      * @param mode The mode to change to.
146      * @throws IllegalArgumentException if the mode is invalid.
147      */
setMode(int mode)148     public void setMode(int mode) {
149         if (mode < 0 || mode > MODE_LAST) {
150             throw new IllegalArgumentException("Invalid mode");
151         }
152         synchronized (this) {
153             nativeSetMode(mode);
154             mMode = mode;
155         }
156     }
157 
nativeSetMode(int mode)158     private native void nativeSetMode(int mode);
159 
160     // Package-private method used by AudioStream.join().
add(AudioStream stream)161     synchronized void add(AudioStream stream) {
162         if (!mStreams.containsKey(stream)) {
163             try {
164                 AudioCodec codec = stream.getCodec();
165                 String codecSpec = String.format(Locale.US, "%d %s %s", codec.type,
166                         codec.rtpmap, codec.fmtp);
167                 long id = nativeAdd(stream.getMode(), stream.getSocket(),
168                         stream.getRemoteAddress().getHostAddress(),
169                         stream.getRemotePort(), codecSpec, stream.getDtmfType(),
170                         mContext != null ? mContext.getOpPackageName()
171                                 : ActivityThread.currentOpPackageName());
172                 mStreams.put(stream, id);
173             } catch (NullPointerException e) {
174                 throw new IllegalStateException(e);
175             }
176         }
177     }
178 
nativeAdd(int mode, int socket, String remoteAddress, int remotePort, String codecSpec, int dtmfType, String opPackageName)179     private native long nativeAdd(int mode, int socket, String remoteAddress,
180             int remotePort, String codecSpec, int dtmfType, String opPackageName);
181 
182     // Package-private method used by AudioStream.join().
remove(AudioStream stream)183     synchronized void remove(AudioStream stream) {
184         Long id = mStreams.remove(stream);
185         if (id != null) {
186             nativeRemove(id);
187         }
188     }
189 
nativeRemove(long id)190     private native void nativeRemove(long id);
191 
192     /**
193      * Sends a DTMF digit to every {@link AudioStream} in this group. Currently
194      * only event {@code 0} to {@code 15} are supported.
195      *
196      * @throws IllegalArgumentException if the event is invalid.
197      */
sendDtmf(int event)198     public void sendDtmf(int event) {
199         if (event < 0 || event > 15) {
200             throw new IllegalArgumentException("Invalid event");
201         }
202         synchronized (this) {
203             nativeSendDtmf(event);
204         }
205     }
206 
nativeSendDtmf(int event)207     private native void nativeSendDtmf(int event);
208 
209     /**
210      * Removes every {@link AudioStream} in this group.
211      */
clear()212     public void clear() {
213         for (AudioStream stream : getStreams()) {
214             stream.join(null);
215         }
216     }
217 
218     @Override
finalize()219     protected void finalize() throws Throwable {
220         nativeRemove(0L);
221         super.finalize();
222     }
223 }
224