| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.net.rtp; |
| |
| import android.app.ActivityThread; |
| import android.media.AudioManager; |
| |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| /** |
| * An AudioGroup is an audio hub for the speaker, the microphone, and |
| * {@link AudioStream}s. Each of these components can be logically turned on |
| * or off by calling {@link #setMode(int)} or {@link RtpStream#setMode(int)}. |
| * The AudioGroup will go through these components and process them one by one |
| * within its execution loop. The loop consists of four steps. First, for each |
| * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming |
| * packets and stores in its buffer. Then, if the microphone is enabled, |
| * processes the recorded audio and stores in its buffer. Third, if the speaker |
| * is enabled, mixes all AudioStream buffers and plays back. Finally, for each |
| * AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other |
| * buffers and sends back the encoded packets. An AudioGroup does nothing if |
| * there is no AudioStream in it. |
| * |
| * <p>Few things must be noticed before using these classes. The performance is |
| * highly related to the system load and the network bandwidth. Usually a |
| * simpler {@link AudioCodec} costs fewer CPU cycles but requires more network |
| * bandwidth, and vise versa. Using two AudioStreams at the same time doubles |
| * not only the load but also the bandwidth. The condition varies from one |
| * device to another, and developers should choose the right combination in |
| * order to get the best result.</p> |
| * |
| * <p>It is sometimes useful to keep multiple AudioGroups at the same time. For |
| * example, a Voice over IP (VoIP) application might want to put a conference |
| * call on hold in order to make a new call but still allow people in the |
| * conference call talking to each other. This can be done easily using two |
| * AudioGroups, but there are some limitations. Since the speaker and the |
| * microphone are globally shared resources, only one AudioGroup at a time is |
| * allowed to run in a mode other than {@link #MODE_ON_HOLD}. The others will |
| * be unable to acquire these resources and fail silently.</p> |
| * |
| * <p class="note">Using this class requires |
| * {@link android.Manifest.permission#RECORD_AUDIO} permission. Developers |
| * should set the audio mode to {@link AudioManager#MODE_IN_COMMUNICATION} |
| * using {@link AudioManager#setMode(int)} and change it back when none of |
| * the AudioGroups is in use.</p> |
| * |
| * @see AudioStream |
| */ |
| public class AudioGroup { |
| /** |
| * This mode is similar to {@link #MODE_NORMAL} except the speaker and |
| * the microphone are both disabled. |
| */ |
| public static final int MODE_ON_HOLD = 0; |
| |
| /** |
| * This mode is similar to {@link #MODE_NORMAL} except the microphone is |
| * disabled. |
| */ |
| public static final int MODE_MUTED = 1; |
| |
| /** |
| * This mode indicates that the speaker, the microphone, and all |
| * {@link AudioStream}s in the group are enabled. First, the packets |
| * received from the streams are decoded and mixed with the audio recorded |
| * from the microphone. Then, the results are played back to the speaker, |
| * encoded and sent back to each stream. |
| */ |
| public static final int MODE_NORMAL = 2; |
| |
| /** |
| * This mode is similar to {@link #MODE_NORMAL} except the echo suppression |
| * is enabled. It should be only used when the speaker phone is on. |
| */ |
| public static final int MODE_ECHO_SUPPRESSION = 3; |
| |
| private static final int MODE_LAST = 3; |
| |
| private final Map<AudioStream, Long> mStreams; |
| private int mMode = MODE_ON_HOLD; |
| |
| private long mNative; |
| static { |
| System.loadLibrary("rtp_jni"); |
| } |
| |
| /** |
| * Creates an empty AudioGroup. |
| */ |
| public AudioGroup() { |
| mStreams = new HashMap<AudioStream, Long>(); |
| } |
| |
| /** |
| * Returns the {@link AudioStream}s in this group. |
| */ |
| public AudioStream[] getStreams() { |
| synchronized (this) { |
| return mStreams.keySet().toArray(new AudioStream[mStreams.size()]); |
| } |
| } |
| |
| /** |
| * Returns the current mode. |
| */ |
| public int getMode() { |
| return mMode; |
| } |
| |
| /** |
| * Changes the current mode. It must be one of {@link #MODE_ON_HOLD}, |
| * {@link #MODE_MUTED}, {@link #MODE_NORMAL}, and |
| * {@link #MODE_ECHO_SUPPRESSION}. |
| * |
| * @param mode The mode to change to. |
| * @throws IllegalArgumentException if the mode is invalid. |
| */ |
| public void setMode(int mode) { |
| if (mode < 0 || mode > MODE_LAST) { |
| throw new IllegalArgumentException("Invalid mode"); |
| } |
| synchronized (this) { |
| nativeSetMode(mode); |
| mMode = mode; |
| } |
| } |
| |
| private native void nativeSetMode(int mode); |
| |
| // Package-private method used by AudioStream.join(). |
| synchronized void add(AudioStream stream) { |
| if (!mStreams.containsKey(stream)) { |
| try { |
| AudioCodec codec = stream.getCodec(); |
| String codecSpec = String.format(Locale.US, "%d %s %s", codec.type, |
| codec.rtpmap, codec.fmtp); |
| long id = nativeAdd(stream.getMode(), stream.getSocket(), |
| stream.getRemoteAddress().getHostAddress(), |
| stream.getRemotePort(), codecSpec, stream.getDtmfType(), |
| ActivityThread.currentOpPackageName()); |
| mStreams.put(stream, id); |
| } catch (NullPointerException e) { |
| throw new IllegalStateException(e); |
| } |
| } |
| } |
| |
| private native long nativeAdd(int mode, int socket, String remoteAddress, |
| int remotePort, String codecSpec, int dtmfType, String opPackageName); |
| |
| // Package-private method used by AudioStream.join(). |
| synchronized void remove(AudioStream stream) { |
| Long id = mStreams.remove(stream); |
| if (id != null) { |
| nativeRemove(id); |
| } |
| } |
| |
| private native void nativeRemove(long id); |
| |
| /** |
| * Sends a DTMF digit to every {@link AudioStream} in this group. Currently |
| * only event {@code 0} to {@code 15} are supported. |
| * |
| * @throws IllegalArgumentException if the event is invalid. |
| */ |
| public void sendDtmf(int event) { |
| if (event < 0 || event > 15) { |
| throw new IllegalArgumentException("Invalid event"); |
| } |
| synchronized (this) { |
| nativeSendDtmf(event); |
| } |
| } |
| |
| private native void nativeSendDtmf(int event); |
| |
| /** |
| * Removes every {@link AudioStream} in this group. |
| */ |
| public void clear() { |
| for (AudioStream stream : getStreams()) { |
| stream.join(null); |
| } |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| nativeRemove(0L); |
| super.finalize(); |
| } |
| } |