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