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