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