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 */ 104 public AudioGroup() { 105 mStreams = new HashMap<AudioStream, Integer>(); 106 } 107 108 /** 109 * Returns the {@link AudioStream}s in this group. 110 */ 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 */ 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 */ 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 142 private native void nativeSetMode(int mode); 143 144 // Package-private method used by AudioStream.join(). 145 synchronized void add(AudioStream stream, AudioCodec codec, int dtmfType) { 146 if (!mStreams.containsKey(stream)) { 147 try { 148 int socket = stream.dup(); 149 String codecSpec = String.format("%d %s %s", codec.type, 150 codec.rtpmap, codec.fmtp); 151 nativeAdd(stream.getMode(), socket, 152 stream.getRemoteAddress().getHostAddress(), 153 stream.getRemotePort(), codecSpec, dtmfType); 154 mStreams.put(stream, socket); 155 } catch (NullPointerException e) { 156 throw new IllegalStateException(e); 157 } 158 } 159 } 160 161 private native void nativeAdd(int mode, int socket, String remoteAddress, 162 int remotePort, String codecSpec, int dtmfType); 163 164 // Package-private method used by AudioStream.join(). 165 synchronized void remove(AudioStream stream) { 166 Integer socket = mStreams.remove(stream); 167 if (socket != null) { 168 nativeRemove(socket); 169 } 170 } 171 172 private native void nativeRemove(int socket); 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 */ 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 189 private native void nativeSendDtmf(int event); 190 191 /** 192 * Removes every {@link AudioStream} in this group. 193 */ 194 public void clear() { 195 synchronized (this) { 196 mStreams.clear(); 197 nativeRemove(-1); 198 } 199 } 200 201 @Override 202 protected void finalize() throws Throwable { 203 clear(); 204 super.finalize(); 205 } 206 } 207