Home | History | Annotate | Download | only in rtp
      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